WPF给ListBox中的每一项添加右键菜单功能
前言
一、ContextMenu控件
ContextMenu 是实现在控件上右键点击时显示一个上下文菜单。
ContextMenu菜单可以添加多个菜单项(MenuItem),每个菜单项可以绑定指定事件或命令。
二、ContextMenu 的 DataContext 绑定问题
案例:
1、在ListBox 控件中创建DataTemplate,绑定数据源为DataCollection。
2、在ListBox创建了TextBlock控件,
3、在TextBlock里面添加ContextMenu右键菜单。
ContextMenu 默认不继承父控件的 DataContext,如果需要显式地绑定 DataContext,可以使用 PlacementTarget 逐级向上绑定到 DataContext。
DataContext="{Binding PlacementTarget.DataContext, RelativeSource={RelativeSource Self}}"
但是,在此案例中如果将 【MenuDownItemCommand】命令创建在 【DataListViewModel 】中,然后对菜单项进行命令绑定,是无法生效的。
<MenuItem Name="Delete" Header="删除" Command="{Binding MenuDownItemCommand}" CommandParameter="Delete"/>
因为ListBox绑定的是DataCollection,DataCollection集合中的对象是DataModel ,而DataModel中又没有创建MenuDownItemCommand命令。所以正确的做法是将MenuDownItemCommand 创建在DataModel这个类中。
public class DataModel : PropertyChangedBase
{
public string Content { get; set; }
public string Date { get; set; }
public ICommand MenuDownItemCommand { get; set; }
}
下面是一个在WPF中使用ContextMenu右键菜单的例子:
该案例使用DataContext数据上下文实现前后端绑定。
1、创建一个属性变更基类 PropertyChangedBase,实现属性变革事件
2、创建数据集合视图模型类,处理视图视图和模型之间的数据交互
3、创建DataModel类,作为数据模型,保存到集合中。
4、创建命令,实现命令绑定,触发相关操作。
三、效果预览
1、界面效果
2、左键
3、右键
四、前端页面代码
<UserControl x:Class="CS学习之WPF右键菜单.DataListView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:CS学习之WPF右键菜单"
xmlns:behavior="http://schemas.microsoft.com/xaml/behaviors"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800">
<UserControl.DataContext>
<local:DataListViewModel x:Name="ViewModel"/>
</UserControl.DataContext>
<Grid Background="#FFFFFF">
<ScrollViewer Background="#AEAEAE" x:Name="RecordScrollViewer" HorizontalScrollBarVisibility="Visible"
VerticalScrollBarVisibility="Visible">
<ListBox Margin="5" ItemsSource="{Binding DataCollection}">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Vertical" Margin="10,5,0,0">
<TextBlock Name="TitleDate" FontSize="14" Margin="10,5,0,0" Foreground="#BBBBBB" Text="{Binding Date}" >
</TextBlock>
<!-- 显示消息内容 -->
<TextBlock FontSize="14" Margin="10,5,0,0"
Text="{Binding Content}" >
<TextBlock.ContextMenu>
<ContextMenu Name="RightKeyMenu" DataContext="{Binding PlacementTarget.DataContext,RelativeSource={RelativeSource Self}}">
<MenuItem Name="Delete" Header="删除"
Command="{Binding MenuDownItemCommand}" CommandParameter="{Binding}" />
<Separator/>
</ContextMenu>
</TextBlock.ContextMenu>
<behavior:Interaction.Triggers>
<!--鼠标点击命令事件-->
<behavior:EventTrigger EventName="PreviewMouseDown">
<behavior:InvokeCommandAction
Command="{Binding DataContext.DataMouseDownCommand,
RelativeSource={RelativeSource AncestorType=ListBox}}"
PassEventArgsToCommand="True">
</behavior:InvokeCommandAction>
</behavior:EventTrigger>
</behavior:Interaction.Triggers>
</TextBlock>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</ScrollViewer>
</Grid>
</UserControl>
1、属性变更 == PropertyChangedBase
///属性变更基类
public class PropertyChangedBase: INotifyPropertyChanged
{
public event PropertyChangedEventHandler? PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
2、数据集合视图模型 == DataListViewModel
public class DataListViewModel : PropertyChangedBase
{
private ObservableCollection<DataModel> _dataCollection;
public ObservableCollection<DataModel> DataCollection
{
get => _dataCollection;
set
{
if (_dataCollection != value)
{
_dataCollection = value;
OnPropertyChanged();
}
}
}
public ICommand DataMouseDownCommand { get; set; }
//无参构造
public DataListViewModel()
{
Initialize();
}
/// <summary>
/// 初始化数据
/// </summary>
private void Initialize()
{
DataMouseDownCommand = new RelayCommand(OnDataMouseDown);
DataCollection = new ObservableCollection<DataModel>();
for (int i = 0; i < 10; i++)
{
DataModel dataModel = new DataModel();
dataModel.Date = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss");
dataModel.Content = $"测试数据0000{i}";
dataModel.MenuDownItemCommand = new RelayCommand(OnMenuItemMouseDown);
DataCollection.Add(dataModel);
}
}
private void OnDataMouseDown(object args)
{
//业务处理
}
public void OnMenuItemMouseDown(object args)
{
//业务处理
MessageBox.Show("右键菜单被点击了");
}
}
3、数据模型类 == DataModel
public class DataModel : PropertyChangedBase
{
public string Content { get; set; }
public string Date { get; set; }
public ICommand MenuDownItemCommand { get; set; }
}
4、命令 == RelayCommand
public class RelayCommand : ICommand
{
private readonly Action<object> _execute;
private readonly Predicate<object> _canExecute;
public RelayCommand(Action<object> execute, Predicate<object> canExecute = null)
{
_execute = execute ?? throw new ArgumentNullException(nameof(execute));
_canExecute = canExecute;
}
public bool CanExecute(object parameter)
{
return _canExecute == null || _canExecute(parameter);
}
public void Execute(object parameter)
{
_execute(parameter);
}
public event EventHandler CanExecuteChanged
{
add { CommandManager.RequerySuggested += value; }
remove { CommandManager.RequerySuggested -= value; }
}
}
五、总结
可以使用ContextMenu 在控件上实现右键点击显示菜单。
可以给菜单添加多个菜单项(MenuItem),每个菜单项可以绑定事件或命令。
ContextMenu 默认不继承父控件的 DataContext,但可以使用 PlacementTarget 逐级向上绑定到 DataContext。