-
I want to implement a "Delete" context flyout for each item in a ListView. The command of the flyout item should be bound to the DeleteFileCommand of the page's view model. To ensure that the correct item is deleted, I need to pass the DataContext of the list view item as a command parameter. However, with the code below, I'm encountering a null parameter in DeleteFileCommand. How can I resolve this? new ListView()
.SelectionMode(ListViewSelectionMode.Single)
.ItemsSource(() => vm.Files)
.ItemTemplate<ManagedTranslateFile>(f =>
new Grid()
.Name(out var grid)
.RowDefinitions("Auto,Auto,Auto")
.HorizontalAlignment(HorizontalAlignment.Stretch)
.VerticalAlignment(VerticalAlignment.Center)
.Margin(8)
.RowSpacing(0)
.ContextFlyout(
new MenuFlyout()
.Items(
new MenuFlyoutItem()
.DataContext(page.DataContext)
.Text("Delete")
.Command(() => vm.DeleteFileCommand)
.CommandParameter(grid.DataContext)
)
)
//... |
Beta Was this translation helpful? Give feedback.
Replies: 3 comments 1 reply
-
Figured out an extremely ugly workaround: public sealed partial class FileListPage : Page
{
private ListView? listView = null;
public FileListPage()
{
this.DataContext<FileListViewModel>((page, vm) => page
.Content(new ListView()
.Name(out listView)
.ItemTemplate<MyFile>(f =>
new Grid()
));
this.Loaded += (s, e) =>
{
if (listView != null)
{
listView.ContainerContentChanging += ListView_ContainerContentChanging;
}
};
}
private void ListView_ContainerContentChanging(ListViewBase sender, ContainerContentChangingEventArgs args)
{
if (args.ItemContainer is ListViewItem item)
{
if (args.InRecycleQueue)
{
return;
}
else
{
SetMenuFlyout(item);
}
}
}
private void SetMenuFlyout(ListViewItem item)
{
var visual = VisualTreeHelper.GetChild(item, 0);
var contentPresenter = FindChildControl<ContentPresenter>(visual);
if (contentPresenter != null && contentPresenter.Name == "ContentPresenter")
{
var content = VisualTreeHelper.GetChild(contentPresenter, 0);
if (content is Grid grid)
{
var flyout = new MenuFlyout()
.Items(
new MenuFlyoutItem()
.Text("Delete")
.Command(x => x.Source(this).Binding(() => this.DataContext).Convert(obj => ((FileListViewModel)obj).DeleteFileCommand))
.CommandParameter(() => grid.DataContext)
);
grid.ContextFlyout = flyout;
grid.DataContextChanged += (s, e) =>
{
if (e.NewValue is MyFile file)
{
var menuItem = flyout.Items[0] as MenuFlyoutItem;
menuItem.CommandParameter = file;
}
};
}
}
}
private T FindChildControl<T>(DependencyObject parent) where T : DependencyObject
{
var count = VisualTreeHelper.GetChildrenCount(parent);
for (int i = 0; i < count; i++)
{
var child = VisualTreeHelper.GetChild(parent, i);
if (child != null && child is T)
{
return (T)child;
}
else
{
T childOfChild = FindChildControl<T>(child);
if (childOfChild != null)
{
return childOfChild;
}
}
}
return default;
}
} |
Beta Was this translation helpful? Give feedback.
-
cc @dansiegel, @dr1rrb |
Beta Was this translation helpful? Give feedback.
-
You can bind both Command and CommandParameter as such: new MenuFlyoutItem()
//.DataContext(page.DataContext) // remember to remove this line
.Command(x => x.Source(page).DataContext<MainVM>().Binding(() => vm.DeleteCommand))
.CommandParameter(() => f) full examplepublic sealed partial class MainPage : Page
{
public MainPage()
{
this.DataContext = new MainVM();
this.DataContext<MainVM>((page, vm) => page
.Background(ThemeResource.Get<Brush>("ApplicationPageBackgroundThemeBrush"))
.Content(new ListView()
.ItemsSource(() => vm.Files)
.ItemTemplate<File>(item => new Grid()
.Children(new TextBlock().Text(() => item.Name))
.ContextFlyout(new MenuFlyout().Items(
new MenuFlyoutItem()
.Text("Delete")
.Command(x => x.Source(page).DataContext<MainVM>().Binding(() => vm.DeleteCommand)) // ok
.CommandParameter(() => item)
))
)
)
);
}
}
internal class MainVM
{
public File[] Files { get; } = "Asd,Qwe,Zxc".Split(',').Select(x => new File(x)).ToArray();
public object DeleteCommand => RelayCommand.Create(DeleteImpl);
private void DeleteImpl(object? obj) { }
}
public record File(string Name);
public class RelayCommand : ICommand
{
private readonly Action<object?> _execute;
public RelayCommand(Action<object?> execute) => _execute = execute;
public static ICommand Create(Action<object?> execute) => new RelayCommand(execute);
public event EventHandler? CanExecuteChanged;
public bool CanExecute(object? parameter) => true;
public void Execute(object? parameter) => _execute?.Invoke(parameter);
} |
Beta Was this translation helpful? Give feedback.
You can bind both Command and CommandParameter as such:
full example