context menu creation in code

10,525

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.

Share:
10,525
Berryl
Author by

Berryl

Updated on June 04, 2022

Comments

  • Berryl
    Berryl about 2 years

    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;
        }