Контекстные меню
Контролы TreeList и TreeView поддерживают всплывающие меню, вызываемые при щелчке правой кнопкой мыши по контролам. Вы можете настроить эти меню таким образом, чтобы предоставлять пользователям пользовательские контекстно-зависимые команды.
Встроенное контекстное меню для заголовка столбца (TreeList)
Когда пользователь щелкает правой кнопкой мыши заголовок столбца TreeList, контрол отображает встроенное меню заголовка столбца. Он содержит команды, которые позволяют пользователю изменять свойства сортировки для выбранного столбца.
Используйте свойство TreeListControl.ColumnMenu
, чтобы настроить это меню.
Вы можете назначить объект Eremex.AvaloniaUI.Controls.Bars.PopupMenu
свойству TreeListControl.ColumnMenu
, чтобы заменить меню по умолчанию.
Чтобы настроить существующее меню заголовка столбца (добавить или удалить элементы по умолчанию), откройте меню после его инициализации (например, в обработчике событий Initialized
вашего TreeList), а затем измените меню.
Пример - Как заменить дефолтное меню для заголовка столбца
Следующий код создает пользовательское меню заголовка столбца, содержащее элемент меню Clear Column Data. Код привязывает элемент меню к команде ClearColumnDataCommand, определенной в ViewModel.
Свойство DataContext
заголовка элементов меню столбца (объекты ToolbarButtonItem
в этом примере) содержит объект TreeListColumn
, для которого было вызвано меню. DataContext
объектов TreeListColumn
соответствует DataContext
в TreeList (объект ViewModel в этом примере).
xmlns:mxtl="clr-namespace:Eremex.AvaloniaUI.Controls.TreeList;assembly=Eremex.Avalonia.Controls"
xmlns:mxe="clr-namespace:Eremex.AvaloniaUI.Controls.Editors;assembly=Eremex.Avalonia.Controls"
xmlns:mxb="clr-namespace:Eremex.AvaloniaUI.Controls.Bars;assembly=Eremex.Avalonia.Controls"
<mxtl:TreeListControl Name="treeList1"
>
<mxtl:TreeListControl.ColumnMenu>
<mxb:PopupMenu>
<mxb:ToolbarButtonItem
Header="Clear Column Data"
Command="{Binding DataContext.ClearColumnDataCommand}"
CommandParameter="{Binding FieldName}">
</mxb:ToolbarButtonItem>
</mxb:PopupMenu>
</mxtl:TreeListControl.ColumnMenu>
</mxtl:TreeListControl>
public MainView()
{
DataContext = new ViewModel();
}
public partial class ViewModel : ObservableObject
{
[RelayCommand]
void ClearColumnData(string fieldName)
{
//...
}
}
Пример - Как изменить существующее меню для заголовка столбца
Следующий пример добавляет пользовательскую команду в дефолтное меню заголовка столбца.
В примере обрабатывается событие TreeListControl.Initialized
для доступа к дефолтному меню заголовка столбца после его инициализации и добавляется команда Refresh Data в меню.
xmlns:mxtl="clr-namespace:Eremex.AvaloniaUI.Controls.TreeList;assembly=Eremex.Avalonia.Controls"
xmlns:mxe="clr-namespace:Eremex.AvaloniaUI.Controls.Editors;assembly=Eremex.Avalonia.Controls"
xmlns:mxb="clr-namespace:Eremex.AvaloniaUI.Controls.Bars;assembly=Eremex.Avalonia.Controls"
<mxtl:TreeListControl Name="treeList1"
Initialized="OnInitialized"
>
using Eremex.AvaloniaUI.Controls.Bars;
using Eremex.AvaloniaUI.Controls.TreeList;
private void OnInitialized(object? sender, System.EventArgs e)
{
ToolbarButtonItem btn1 = new ToolbarButtonItem();
btn1.Header = "Refresh Data";
btn1.ShowSeparator = true;
btn1.Command = new RelayCommand<TreeListControl>(UpdateTreeList);
btn1.CommandParameter = treeList1;
treeList1.ColumnMenu.Items.Add(btn1);
}
[RelayCommand]
void UpdateTreeList(TreeListControl treeList)
{
//...
}
Меню для ячеек строк (TreeList и TreeView)
Контролы TreeList и TreeView имеют встроенное меню ячеек строк, заданное свойством TreeListControlBase.RowCellMenu
. Изначально это меню пусто. Чтобы отобразить это меню, заполните его элементами в XAML или в code behind.
Пример - Как отобразить одинаковые команды контекстного меню для всех строк
Следующий пример добавляет команду "Delete Row" в меню ячейки строки (TreeListControlBase.RowCellMenu
) для всех строк.
Приведенный ниже код XAML инициализирует свойство TreeListControlBase.RowCellMenu
с помощью объекта PopupMenu
. Всплывающее меню определяет один элемент (ToolbarButtonItem
), свойства которого определяют заголовок элемента, команду и параметр команды.
Когда вызывается меню, его DataContext
содержит специальный объект CellData
, который позволяет вам получить доступ к информации, зависящей от контекста:
- Средство настройки свойств
Command
показывает, как получить доступ к контролу (объектуTreeListControl
),DataContext
контрола и команде DeleteRow, определенной в объектеDataContext
контрола (DataContext
контрола содержит экземпляр класса ViewModel). - Установщик
CommandParameter
показывает, как получить доступ к нижележащему бизнес-объекту (строке данных), который соответствует выбранному узлу.
xmlns:mxtl="clr-namespace:Eremex.AvaloniaUI.Controls.TreeList;assembly=Eremex.Avalonia.Controls"
xmlns:mxe="clr-namespace:Eremex.AvaloniaUI.Controls.Editors;assembly=Eremex.Avalonia.Controls"
xmlns:mxb="clr-namespace:Eremex.AvaloniaUI.Controls.Bars;assembly=Eremex.Avalonia.Controls"
<mxtl:TreeListControl Name="treeList1"
AutoGenerateColumns="True"
ItemsSource="{Binding Departments}"
ChildrenFieldName="Children"
HasChildrenFieldName="HasChildren"
>
<mxtl:TreeListControl.RowCellMenu>
<mxb:PopupMenu>
<mxb:PopupMenu.Items>
<mxb:ToolbarButtonItem Header="Delete Row"
Command="{Binding DataControl.DataContext.DeleteRowCommand}"
CommandParameter="{Binding Row}"
>
</mxb:ToolbarButtonItem>
</mxb:PopupMenu.Items>
</mxb:PopupMenu>
</mxtl:TreeListControl.RowCellMenu>
</mxtl:TreeListControl>
public partial class MainView : UserControl
{
ViewModel viewModel = new ViewModel();
public MainView()
{
DataContext = viewModel;
Department depOperations = new Department() { Name = "Operations", Phone = "1110", IsRoot = true };
Department depManufacturing = new Department() { Name = "Manufacturing", Phone = "1111" };
Department depQuality = new Department() { Name = "Quality", Phone = "1112" };
depOperations.Children.Add(depManufacturing);
depOperations.Children.Add(depQuality);
Department depMarketing = new Department() { Name = "Marketing", Phone = "3120", IsRoot = true };
Department depSales = new Department() { Name = "Sales", Phone = "3121" };
Department depCRM = new Department() { Name = "CRM", Phone = "3122" };
depMarketing.Children.Add(depSales);
depMarketing.Children.Add(depCRM);
Department depAccountsAndFinance = new Department() { Name = "Accounts & Finance", Phone = "5780", IsRoot = true };
Department depAccounts = new Department() { Name = "Sales", Phone = "5781" };
Department depFinance = new Department() { Name = "Finance", Phone = "5782" };
depAccountsAndFinance.Children.Add(depAccounts);
depAccountsAndFinance.Children.Add(depFinance);
Department depHumanResources = new Department() { Name = "Human Resources", Phone = "7370", IsRoot = true };
Department depHR = new Department() { Name = "HR", Phone = "7370" };
depHumanResources.Children.Add(depHR);
viewModel.Departments.Add(depOperations);
viewModel.Departments.Add(depMarketing);
viewModel.Departments.Add(depAccountsAndFinance);
viewModel.Departments.Add(depHumanResources);
InitializeComponent();
}
}
public partial class ViewModel : ObservableObject
{
public ViewModel() { }
public ObservableCollection<Department> Departments { get; } = new();
[RelayCommand]
void DeleteRow(Department row)
{
DeleteRowRecursively(Departments, row);
}
bool DeleteRowRecursively(ObservableCollection<Department> departments, Department row)
{
foreach (Department dep in departments)
{
if (dep == row)
{
departments.Remove(row);
return true;
}
if (DeleteRowRecursively(dep.Children, row))
break;
}
return false;
}
}
public partial class Department : ObservableObject
{
[ObservableProperty]
public string name = "";
[ObservableProperty]
public string phone = "0";
[Browsable(false)]
public bool IsRoot { get; set; } = false;
public ObservableCollection<Department> Children { get; } = new();
public bool HasChildren { get { return Children.Count > 0; } }
public void AddDepartment(Department department)
{
Children.Add(department);
if (Children.Count == 1)
OnPropertyChanged(nameof(HasChildren));
}
}
Пример - Как отобразить разные команды контекстного меню для разных строк
В следующем примере показаны различные команды в контекстном меню (TreeListControlBase.RowCellMenu
) для корневой и вложенной строк. В корневом контекстном меню отображается команда "Add Child Dep", в то время как в контекстном меню для вложенных строк отображается команда "Delete Row".
Код XAML определяет контекстное меню (объект PopupMenu
) с помощью команд "Add Child Dep" и "Delete Row".
Команда "Add Child Dep" отображается для корневых узлов. Команда "Delete Row" отображается для вложенных узлов.
Видимостью этих команд динамически управляет свойство IsRoot бизнес-объекта.
xmlns:mxtl="clr-namespace:Eremex.AvaloniaUI.Controls.TreeList;assembly=Eremex.Avalonia.Controls"
xmlns:mxe="clr-namespace:Eremex.AvaloniaUI.Controls.Editors;assembly=Eremex.Avalonia.Controls"
xmlns:mxb="clr-namespace:Eremex.AvaloniaUI.Controls.Bars;assembly=Eremex.Avalonia.Controls"
<mxtl:TreeListControl Name="treeList1"
AutoGenerateColumns="True"
ItemsSource="{Binding Departments}"
ChildrenFieldName="Children"
HasChildrenFieldName="HasChildren"
>
<mxtl:TreeListControl.RowCellMenu>
<mxb:PopupMenu>
<mxb:PopupMenu.Items>
<mxb:ToolbarButtonItem
Header="Add Child Dep"
Command="{Binding DataControl.DataContext.AddChildRowCommand}"
CommandParameter="{Binding Row}"
Glyph="/Assets/add.png"
IsVisible="{Binding Row.IsRoot}">
</mxb:ToolbarButtonItem>
<mxb:ToolbarButtonItem
Header="Delete Row"
Command="{Binding DataControl.DataContext.DeleteRowCommand}"
CommandParameter="{Binding Row}"
Glyph="/Assets/delete.png"
IsVisible="{Binding !Row.IsRoot}">
</mxb:ToolbarButtonItem>
</mxb:PopupMenu.Items>
</mxb:PopupMenu>
</mxtl:TreeListControl.RowCellMenu>
</mxtl:TreeListControl>
public partial class MainView : UserControl
{
ViewModel viewModel = new ViewModel();
public MainView()
{
DataContext = viewModel;
Department depOperations = new Department() { Name = "Operations", Phone = "1110", IsRoot = true };
Department depManufacturing = new Department() { Name = "Manufacturing", Phone = "1111" };
Department depQuality = new Department() { Name = "Quality", Phone = "1112" };
depOperations.Children.Add(depManufacturing);
depOperations.Children.Add(depQuality);
Department depMarketing = new Department() { Name = "Marketing", Phone = "3120", IsRoot = true };
Department depSales = new Department() { Name = "Sales", Phone = "3121" };
Department depCRM = new Department() { Name = "CRM", Phone = "3122" };
depMarketing.Children.Add(depSales);
depMarketing.Children.Add(depCRM);
Department depAccountsAndFinance = new Department() { Name = "Accounts & Finance", Phone = "5780", IsRoot = true };
Department depAccounts = new Department() { Name = "Sales", Phone = "5781" };
Department depFinance = new Department() { Name = "Finance", Phone = "5782" };
depAccountsAndFinance.Children.Add(depAccounts);
depAccountsAndFinance.Children.Add(depFinance);
Department depHumanResources = new Department() { Name = "Human Resources", Phone = "7370", IsRoot = true };
Department depHR = new Department() { Name = "HR", Phone = "7370" };
depHumanResources.Children.Add(depHR);
viewModel.Departments.Add(depOperations);
viewModel.Departments.Add(depMarketing);
viewModel.Departments.Add(depAccountsAndFinance);
viewModel.Departments.Add(depHumanResources);
InitializeComponent();
}
}
public partial class ViewModel : ObservableObject
{
public ViewModel() { }
public ObservableCollection<Department> Departments { get; } = new();
[RelayCommand]
void AddChildRow(Department parentRow)
{
parentRow.AddDepartment(new Department() { Name = "New dep", Phone = "0000" });
}
[RelayCommand]
void DeleteRow(Department row)
{
DeleteRowRecursively(Departments, row);
}
bool DeleteRowRecursively(ObservableCollection<Department> departments, Department row)
{
foreach (Department dep in departments)
{
if (dep == row)
{
departments.Remove(row);
return true;
}
if (DeleteRowRecursively(dep.Children, row))
break;
}
return false;
}
}
public partial class Department : ObservableObject
{
[ObservableProperty]
public string name = "";
[ObservableProperty]
public string phone = "0";
[Browsable(false)]
public bool IsRoot { get; set; } = false;
public ObservableCollection<Department> Children { get; } = new();
public bool HasChildren { get { return Children.Count > 0; } }
public void AddDepartment(Department department)
{
Children.Add(department);
if (Children.Count == 1)
OnPropertyChanged(nameof(HasChildren));
}
}
Пример - Как генерировать команды контекстного меню из ViewModel
В этом примере показано, как заполнить меню ячейки строки контрола TreeList элементами, определенными в ViewModel.
В примере свойству TreeListControlBase.RowCellMenu
присваивается значение объекта PopupMenu
. Меню заполнено элементами ToolbarButtonItem
, созданными из объектов, указанных в коллекции PopupMenu.ItemsSource
.
Свойство PopupMenu.ItemsSource
определяет источник объектов, отображаемых в виде всплывающих элементов меню. Код XAML связывает свойство PopupMenu.ItemsSource
со свойством ViewModel.MenuItems, используя следующее выражение привязки:
<mxb:PopupMenu ItemsSource="{Binding DataControl.DataContext.MenuItems}">
Когда для ячейки TreeList отображается PopupMenu
, DataContext
меню содержит объект Eremex.AvaloniaUI.Controls.TreeList.CellData
. Объект CellData
предоставляет свойство DataControl
, которое позволяет вам получить доступ к контролу, для которого отображается меню. Объект CellData
также содержит другие свойства для доступа к информации, связанной с ячейкой (объект столбца, строки и т.д.).
DataContext
из элементов PopupMenu
- это объекты, хранящиеся в коллекции PopupMenu.ItemsSource
(объекты MenuItemViewModel). Следующий код привязывает свойства ToolbarButtonItem
, Header
и Command
к данным, указанным объектами MenuItemViewModel.
<mxb:PopupMenu.Styles>
<Style Selector="mxb|ToolbarButtonItem">
<Setter Property="Header" Value="{Binding Header}"/>
<Setter Property="Command" Value="{Binding Command}" />
</Style>
</mxb:PopupMenu.Styles>
Команде ToolbarButtonItem
требуется информация о строке, по которой был выполнен щелчок правой кнопкой мыши. Чтобы передать строку данных команде, код XAML устанавливает свойство ToolbarButtonItem.CommandParameter
следующим образом:
<mxb:PopupMenu.Styles>
<Style Selector="mxb|ToolbarButtonItem">
...
<Setter Property="CommandParameter" Value="{Binding $parent[mxtl:CellControl].DataContext.Row}" />
</Style>
</mxb:PopupMenu.Styles>
Здесь выражение $parent[mxtl:CellControl]
обходит логическое дерево, чтобы найти объект Eremex.AvaloniaUI.Controls.TreeList.CellControl
(он является родительским для DataContext
ToolbarButtonItem
). Объект CellControl.DataContext
хранит объект Eremex.AvaloniaUI.Controls.TreeList.CellData
, который позволяет вам получить доступ к строке данных из свойства CellData.Row
.
Полный код показан ниже.
xmlns:mxtl="clr-namespace:Eremex.AvaloniaUI.Controls.TreeList;assembly=Eremex.Avalonia.Controls"
xmlns:mxe="clr-namespace:Eremex.AvaloniaUI.Controls.Editors;assembly=Eremex.Avalonia.Controls"
xmlns:mxb="clr-namespace:Eremex.AvaloniaUI.Controls.Bars;assembly=Eremex.Avalonia.Controls"
<mxtl:TreeListControl Name="treeList1"
AutoGenerateColumns="True"
ItemsSource="{Binding Departments}"
ChildrenFieldName="Children"
HasChildrenFieldName="HasChildren"
ExpandStateFieldName="IsExpanded"
FocusedItem="{Binding SelectedDepartment, Mode=TwoWay}"
>
<mxtl:TreeListControl.RowCellMenu>
<mxb:PopupMenu ItemsSource="{Binding DataControl.DataContext.MenuItems}">
<mxb:PopupMenu.Styles>
<Style Selector="mxb|ToolbarButtonItem">
<Setter Property="Header" Value="{Binding Header}"/>
<Setter Property="Command" Value="{Binding Command }" />
<Setter Property="CommandParameter" Value="{Binding $parent[mxtl:CellControl].DataContext.Row}" />
</Style>
</mxb:PopupMenu.Styles>
</mxb:PopupMenu>
</mxtl:TreeListControl.RowCellMenu>
</mxtl:TreeListControl>
public partial class MainView : UserControl
{
ViewModel viewModel = new ViewModel();
public MainView()
{
DataContext = viewModel;
Department depOperations = new Department() { Name = "Operations", Phone = "1110", IsRoot = true };
Department depManufacturing = new Department() { Name = "Manufacturing", Phone = "1111" };
Department depQuality = new Department() { Name = "Quality", Phone = "1112" };
depOperations.Children.Add(depManufacturing);
depOperations.Children.Add(depQuality);
Department depMarketing = new Department() { Name = "Marketing", Phone = "3120", IsRoot = true };
Department depSales = new Department() { Name = "Sales", Phone = "3121" };
Department depCRM = new Department() { Name = "CRM", Phone = "3122" };
depMarketing.Children.Add(depSales);
depMarketing.Children.Add(depCRM);
Department depAccountsAndFinance = new Department() { Name = "Accounts & Finance", Phone = "5780", IsRoot = true };
Department depAccounts = new Department() { Name = "Sales", Phone = "5781" };
Department depFinance = new Department() { Name = "Finance", Phone = "5782" };
depAccountsAndFinance.Children.Add(depAccounts);
depAccountsAndFinance.Children.Add(depFinance);
Department depHumanResources = new Department() { Name = "Human Resources", Phone = "7370", IsRoot = true };
Department depHR = new Department() { Name = "HR", Phone = "7370" };
depHumanResources.Children.Add(depHR);
viewModel.Departments.Add(depOperations);
viewModel.Departments.Add(depMarketing);
viewModel.Departments.Add(depAccountsAndFinance);
viewModel.Departments.Add(depHumanResources);
InitializeComponent();
}
}
public partial class ViewModel : ObservableObject
{
[ObservableProperty]
Department? selectedDepartment;
public ViewModel()
{
MenuItemViewModel menuItem1 = new MenuItemViewModel();
menuItem1.Header = "Add Dep";
menuItem1.Command = new RelayCommand<Department>(AddChildRow);
MenuItems.Add(menuItem1);
}
public ObservableCollection<Department> Departments { get; } = new();
public ObservableCollection<MenuItemViewModel> MenuItems { get; } = new();
[RelayCommand]
void AddChildRow(Department? parentRow)
{
if (parentRow == null) return;
Department newDepartment = new() { Name = "New dep", Phone = "0000" };
parentRow.AddDepartment(newDepartment);
parentRow.IsExpanded = true;
SelectedDepartment = newDepartment;
}
}
public partial class MenuItemViewModel : ObservableObject
{
[ObservableProperty]
string? header;
[ObservableProperty]
ICommand? command;
}
public partial class Department : ObservableObject
{
[ObservableProperty]
public string name = "";
[ObservableProperty]
public string phone = "0";
[ObservableProperty]
public bool isExpanded;
[Browsable(false)]
public bool IsRoot { get; set; } = false;
public ObservableCollection<Department> Children { get; } = new();
public bool HasChildren => Children.Count > 0;
public void AddDepartment(Department department)
{
Children.Add(department);
if (Children.Count == 1)
OnPropertyChanged(nameof(HasChildren));
}
}
Настройка меню при его показе
Вы можете обработать событие PopupMenu.Opening
для динамической настройки меню TreeList. Событие происходит, когда вот-вот отобразится всплывающее меню.
Пример - Как отобразить контекстное меню для первого столбца
В следующем примере свойству TreeListControlBase.RowCellMenu
присваивается пустое значение PopupMenu
, а затем обрабатывается событие PopupMenu.Opening
для заполнения меню элементами, когда пользователь щелкает правой кнопкой мыши ячейки в первом видимом столбце TreeList. Меню остается пустым (и, следовательно, не отображается), когда пользователь щелкает правой кнопкой мыши в других столбцах.
Созданное меню содержит кнопку-переключатель "Show/Hide Root Indent", которая переключает видимость отступа TreeList (свойство TreeListControlBase.ShowRootIndent
).
<mxtl:TreeListControl Name="treeList1" ...>
<mxtl:TreeListControl.RowCellMenu>
<mxb:PopupMenu Opening="RowCellMenuOpening"/>
</mxtl:TreeListControl.RowCellMenu>
</mxtl:TreeListControl>
void RowCellMenuOpening(object? sender, CancelEventArgs e)
{
if (sender == null) return;
PopupMenu menu = sender as PopupMenu;
menu.Items.Clear();
CellData cellData = menu.DataContext as CellData;
TreeListControl control = cellData.DataControl as TreeListControl;
TreeListColumn column = cellData.Column as TreeListColumn;
//// Access the underlying data row, when required.
//object dataRow = cellData.Row;
if(column.VisibleIndex == 0)
{
ToolbarCheckItem btn1 = new ToolbarCheckItem();
btn1.IsChecked = control.ShowRootIndent;
btn1.Header = (control.ShowRootIndent) ? "Hide Root Indent" : "Show Root Indent";
btn1.Command = new RelayCommand<TreeListControl>(ShowRootIndentCommand);
btn1.CommandParameter = control;
menu.Items.Add(btn1);
}
}
[RelayCommand]
void ShowRootIndentCommand(TreeListControl treeList)
{
treeList.ShowRootIndent = !treeList.ShowRootIndent;
}
Отображение контекстного меню для контролов, используя attached-свойство ToolbarManager.
Компонент Eremex.AvaloniaUI.Controls.Bars.ToolbarManager
предоставляет attached-свойство ContextPopup
, которое позволяет назначать контекстное меню любому контролу, включая TreeList и TreeView. Это контекстное меню отображается для регионов TreeList/TreeView, в которых нет контекстных меню по умолчанию, и для регионов с пустыми меню по умолчанию.
Пример - Как назначить контекстное меню, используя attached-свойство ToolbarManager.ContextPopup
Следующий код использует attached-свойство ToolbarManager.ContextPopup
для указания контекстного меню для контрола TreeList. Меню содержит переключающий элемент меню Show Column Header Panel/Hide Column Header Panel, который переключает видимость опции TreeListControl.ShowColumnHeaders
.
<mxtl:TreeListControl Name="treeList1"
>
<mxtl:TreeListControl.Styles>
<Style Selector="mxtl|TreeListControl">
<Setter Property="mxb:ToolbarManager.ContextPopup" >
<Template>
<mxb:PopupMenu Tag="treeList1">
<mxb:ToolbarCheckItem Header="Show Column Header Panel" IsChecked="{Binding $parent[mxtl:TreeListControl].ShowColumnHeaders, Mode=TwoWay}" IsVisible="{Binding !$parent[mxtl:TreeListControl].ShowColumnHeaders}" />
<mxb:ToolbarCheckItem Header="Hide Column Header Panel" IsChecked="{Binding $parent[mxtl:TreeListControl].ShowColumnHeaders, Mode=TwoWay}" IsVisible="{Binding $parent[mxtl:TreeListControl].ShowColumnHeaders}" />
</mxb:PopupMenu>
</Template>
</Setter>
</Style>
</mxtl:TreeListControl.Styles>
</mxtl:TreeListControl>
* Эта страница была создана автоматически с помощью сервиса машинного перевода Яндекс Переводчик.