在代码中创建上下文菜单

发布于 2024-10-29 06:34:02 字数 5617 浏览 0 评论 0原文

我正在将 xaml 中的三个相关但不同的 DataGrid 重构为代码,并遇到更新上下文菜单的标题文本的问题。

命令和文本需要根据哪个数据网格单元是当前单元来更新。标题文本在 xaml 中更新得很好,但正如您从下图中看到的,它现在显示为空字符串。该命令本身确实可以正常工作,并且可以在正确的网格单元上工作。

标题文本触发属性的设置器已更改,但我怀疑我的代码没有像 xaml 等效项那样复制绑定。我也不确定共享属性是否是我需要在代码中考虑的东西。

有谁知道我如何改进我正在使用的代码?

干杯,
Berryl

在此处输入图像描述

XAML 样式建立绑定

<ContextMenu x:Key="NonProjectActivityContextMenu" x:Shared="true">

    <MenuItem 
        DataContext="{Binding MakeEachWeekDayFullDayCommand}" Command="{Binding .}" 
        Header="{Binding HeaderText}" InputGestureText="{Binding InputGestureText}"      
        />
    <MenuItem 
        DataContext="{Binding MakeFullDayCommand}" Command="{Binding .}" 
        Header="{Binding HeaderText}" InputGestureText="{Binding InputGestureText}"      
        />
</ContextMenu>

<!-- Bindings assumes a VmMenuItem (Command Reference) -->
<Style x:Key="ContextMenuItemStyle" TargetType="{x:Type MenuItem}">
    <Setter Property="Header" Value="{Binding HeaderText}"/>
    <Setter Property="InputGestureText" Value="{Binding InputGestureText}" />
    <Setter Property="Command" Value="{Binding Command}" />
    <Setter Property="Icon" Value="{Binding Icon}" />
    <Setter Property="Tag" Value="{Binding IdTag}" />
    <Setter Property="ItemsSource" Value="{Binding Children}"/>
</Style>

代码

    protected virtual ContextMenu _GetContextMenu() {
        var menuItems = _dataContext.MenuItems.Select(menuItem => menuItem.ToMenuItem());
        var cm = new ContextMenu();
        foreach (var item in menuItems) {
            cm.Items.Add(item);
        }
        return cm;
    }

更新

好吧,空字符串部分只是我自己的愚蠢 - 我没有' t 初始化了标题文本!下图是我现在得到的,这是一个改进。文本应该更新为星期几,即“让星期一成为一整天” 在此处输入图像描述

编辑 Erno

我正在设置列和网格本身的样式如下,所以我想我可以只需获取上下文菜单的资源并进行设置即可。

然而,我得到了一个奇怪的结果,正如您从图片中看到的那样 - 就像上下文菜单覆盖了整个网格!

在此处输入图像描述

    private void OnDataGridLoaded(object sender, RoutedEventArgs e)
    {
        _dataContext = (ActivityCollectionViewModel)DataContext;

        IsSynchronizedWithCurrentItem = true;
        Style = (Style)FindResource(GRID_STYLE_NAME);

        _AddColumns();

        var timeSheetColumns = Columns.Cast<TimesheetGridColumn>();
        foreach (var col in timeSheetColumns)
        {
            col.SetHeader();
            col.SetCellStyle(this);
            col.SetBinding();
        }

        if(DesignerProperties.GetIsInDesignMode(this)) {
            // just so the designer doesn't hit a null reference on the data context
            ItemsSource = new ObservableCollection<ActivityViewModel>();
        }
        else {
            // ok, we have a runtime data context to work with
            ItemsSource = _dataContext.ActivityVms;
            InputBindings.AddRange(_GetKeyBindings());
            ContextMenu = _GetContextMenu();
            ContextMenu.Style = (Style)FindResource("ContextMenuItemStyle");
        }
    }


    private void OnDataGridLoaded(object sender, RoutedEventArgs e)
    {
        _dataContext = (ActivityCollectionViewModel)DataContext;

        IsSynchronizedWithCurrentItem = true;
        Style = (Style)FindResource(GRID_STYLE_NAME);

        _AddColumns();

        var timeSheetColumns = Columns.Cast<TimesheetGridColumn>();
        foreach (var col in timeSheetColumns)
        {
            col.SetHeader();
            col.SetCellStyle(this);
            col.SetBinding();
        }

        if(DesignerProperties.GetIsInDesignMode(this)) {
            // just so the designer doesn't hit a null reference on the data context
            ItemsSource = new ObservableCollection<ActivityViewModel>();
        }
        else {
            // ok, we have a runtime data context to work with
            ItemsSource = _dataContext.ActivityVms;
            InputBindings.AddRange(_GetKeyBindings());
            ContextMenu = _GetContextMenu();
            ContextMenu.Style = (Style)FindResource("ContextMenuItemStyle");
        }
    }

最新更新

我尝试根据 这篇文章但没有骰子。我的命令已更新,这意味着它在正确的单元格上执行,但我无法让文本反映它是哪个单元格。我最终决定动态构建上下文菜单,如下所示。它工作得很好,尽管看起来我应该能够做得更好。

我将向埃尔诺给出答案并结束这个问题。

    private void OnCurrentCellChanged(object sender, EventArgs e)
    {
        if (ReferenceEquals(null, sender)) return;
        var grid = (DataGrid)sender;
        var selectedActivity = (ActivityViewModel)grid.CurrentItem;
        if (ReferenceEquals(selectedActivity, null)) return;

        if (_isEditableDayOfTheWeekColumn(grid.CurrentColumn))
        {
            var dowCol = (DayOfTheWeekColumn)grid.CurrentColumn;
            var index = Convert.ToInt32(dowCol.DowIndex);
            selectedActivity.SetSelectedAllocationVm(index);
        }
        else
        {
            selectedActivity.SetSelectedAllocationVm(-1);
        }
        var commands = selectedActivity
            .AllCommands
            .Select(vmMenuItem => vmMenuItem.Command.ToMenuItem());
        var cm = new ContextMenu();
        foreach (var item in commands)
        {
            //item.SetResourceReference(StyleProperty, "ContextMenuItemStyle");
            cm.Items.Add(item);
        }
        grid.ContextMenu = cm;
    }

I am refactoring three related but different DataGrids from xaml into code and hitting an issue updating the header text of a context menu.

The command and text need to update according to which data grid cell is the current cell. The header text updated fine in xaml, but as you can see from the picture below, it now shows up as an empty string. The command itself does work properly, and works on the correct grid cell.

The setter for the header text fires property changed, but I suspect my code is not replicating the binding the way the xaml equivalent does. I'm also not sure if the Shared attribute is something I need account for in code.

Does anyone see how I can improve the code I am using?

Cheers,
Berryl

enter image description here

XAML style to establish bindings

<ContextMenu x:Key="NonProjectActivityContextMenu" x:Shared="true">

    <MenuItem 
        DataContext="{Binding MakeEachWeekDayFullDayCommand}" Command="{Binding .}" 
        Header="{Binding HeaderText}" InputGestureText="{Binding InputGestureText}"      
        />
    <MenuItem 
        DataContext="{Binding MakeFullDayCommand}" Command="{Binding .}" 
        Header="{Binding HeaderText}" InputGestureText="{Binding InputGestureText}"      
        />
</ContextMenu>

<!-- Bindings assumes a VmMenuItem (Command Reference) -->
<Style x:Key="ContextMenuItemStyle" TargetType="{x:Type MenuItem}">
    <Setter Property="Header" Value="{Binding HeaderText}"/>
    <Setter Property="InputGestureText" Value="{Binding InputGestureText}" />
    <Setter Property="Command" Value="{Binding Command}" />
    <Setter Property="Icon" Value="{Binding Icon}" />
    <Setter Property="Tag" Value="{Binding IdTag}" />
    <Setter Property="ItemsSource" Value="{Binding Children}"/>
</Style>

CODE

    protected virtual ContextMenu _GetContextMenu() {
        var menuItems = _dataContext.MenuItems.Select(menuItem => menuItem.ToMenuItem());
        var cm = new ContextMenu();
        foreach (var item in menuItems) {
            cm.Items.Add(item);
        }
        return cm;
    }

UPDATE

Well the empty string part was just my own stupidity - I hadn't initialized the header text! The picture below is what I get now, which is an improvement. The text should update to say the day of the week tho, ie, "Make Monday a full day"
enter image description here

EDIT for Erno

I am setting columns and the style for the grid itself as below, so I thought I can just fetch the resource for the context menu and set it.

Am getting an odd result however, as you can see from the pic - it's like the context menu is covering the whole grid!

enter image description here

    private void OnDataGridLoaded(object sender, RoutedEventArgs e)
    {
        _dataContext = (ActivityCollectionViewModel)DataContext;

        IsSynchronizedWithCurrentItem = true;
        Style = (Style)FindResource(GRID_STYLE_NAME);

        _AddColumns();

        var timeSheetColumns = Columns.Cast<TimesheetGridColumn>();
        foreach (var col in timeSheetColumns)
        {
            col.SetHeader();
            col.SetCellStyle(this);
            col.SetBinding();
        }

        if(DesignerProperties.GetIsInDesignMode(this)) {
            // just so the designer doesn't hit a null reference on the data context
            ItemsSource = new ObservableCollection<ActivityViewModel>();
        }
        else {
            // ok, we have a runtime data context to work with
            ItemsSource = _dataContext.ActivityVms;
            InputBindings.AddRange(_GetKeyBindings());
            ContextMenu = _GetContextMenu();
            ContextMenu.Style = (Style)FindResource("ContextMenuItemStyle");
        }
    }


    private void OnDataGridLoaded(object sender, RoutedEventArgs e)
    {
        _dataContext = (ActivityCollectionViewModel)DataContext;

        IsSynchronizedWithCurrentItem = true;
        Style = (Style)FindResource(GRID_STYLE_NAME);

        _AddColumns();

        var timeSheetColumns = Columns.Cast<TimesheetGridColumn>();
        foreach (var col in timeSheetColumns)
        {
            col.SetHeader();
            col.SetCellStyle(this);
            col.SetBinding();
        }

        if(DesignerProperties.GetIsInDesignMode(this)) {
            // just so the designer doesn't hit a null reference on the data context
            ItemsSource = new ObservableCollection<ActivityViewModel>();
        }
        else {
            // ok, we have a runtime data context to work with
            ItemsSource = _dataContext.ActivityVms;
            InputBindings.AddRange(_GetKeyBindings());
            ContextMenu = _GetContextMenu();
            ContextMenu.Style = (Style)FindResource("ContextMenuItemStyle");
        }
    }

Latest Update

I tried making my binding relative per this SO post but no dice. My command updated, meaning it executed on the correct cell, but I couldn't get the text to reflect which cell it was. I finally just decided to build the context menu on the fly as below. It work fine, although it seems I should have been able to do better.

Am going to give the answer to Erno and close this out.

    private void OnCurrentCellChanged(object sender, EventArgs e)
    {
        if (ReferenceEquals(null, sender)) return;
        var grid = (DataGrid)sender;
        var selectedActivity = (ActivityViewModel)grid.CurrentItem;
        if (ReferenceEquals(selectedActivity, null)) return;

        if (_isEditableDayOfTheWeekColumn(grid.CurrentColumn))
        {
            var dowCol = (DayOfTheWeekColumn)grid.CurrentColumn;
            var index = Convert.ToInt32(dowCol.DowIndex);
            selectedActivity.SetSelectedAllocationVm(index);
        }
        else
        {
            selectedActivity.SetSelectedAllocationVm(-1);
        }
        var commands = selectedActivity
            .AllCommands
            .Select(vmMenuItem => vmMenuItem.Command.ToMenuItem());
        var cm = new ContextMenu();
        foreach (var item in commands)
        {
            //item.SetResourceReference(StyleProperty, "ContextMenuItemStyle");
            cm.Items.Add(item);
        }
        grid.ContextMenu = cm;
    }

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

寒江雪… 2024-11-05 06:34:02

我的猜测是您也想在代码中使用样式。
只需创建 Style 类的实例,设置其属性(包括绑定)并将其添加到树中的 Resources 属性即可。

接下来,在生成的菜单项中使用该样式。

My guess is you want to use a Style in code as well.
Just create an instance of the Style class, set its properties (including the binding) and add it to a Resources property in the tree.

Next, use the style in the generated menu items.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文