2
votes

I'm sure I'm missing something really silly and stupid here, and I'll probably kick myself when I see it, but I just have a simple question.

I've got some code in the constructor of the code-behind for a view with a grid, that does the following:

Grid mainGrid = this.Content as Grid;
MenuItem item = mainGrid.ContextMenu.Items[0] as MenuItem;
this.ApplySkinFromMenuItem(item);

So my question is how can I do this from the ViewModel? The ViewModel doesn't know what "this" is, and doesn't have a reference to "this" either.

It is my understanding that the view model object is created in XAML by calling:

<ObjectDataProvider x:Key="TimersHostViewModel" ObjectType="{x:Type local:TimersHostViewModel}"/>

And setting the data context like so:

<Grid DataContext="{StaticResource TimersHostViewModel}" Style="{DynamicResource styleBackground}">

But this doesn't give the TimersHostViewModel any knowledge about "this.Content", and saying TimersHost.Content doesn't help, because TimersHost isn't an actual object, but a class, and I need an actual object to get the ".Content" from, and it should be the right object, the object that is from the code behind, but how can I get that into the view model?

After all following MVVM means that the ViewModel shouldn't have any knowledge about the View, and the View shouldn't have any knowledge about the ViewModel, and they just communicate back and forth with bindings and INotifyPropertyChanged and other such message passing techniques. I've done a fair bit of this stuff in another application, so I'm some-what familiar with the basics, but still some-what new, and still learning and even re-learning.

I've included the full source below. As you can see I'm in the process of trying to get the code out of the code behind and into the ViewModel, but I'm running into a compiler error when attempting to get this.Content from the main grid.

XAML:

<Window 
x:Class="TimersXP.TimersHost"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"    
xmlns:scm="clr-namespace:System.ComponentModel;assembly=WindowsBase"
xmlns:local="clr-namespace:TimersXP"
Name="TimersHostView"
SizeToContent="Height"
Title="TimersXP"
WindowStartupLocation="CenterScreen"
WindowStyle="ToolWindow">
<Window.Resources>
    <ObjectDataProvider x:Key="TimersHostViewModel" ObjectType="{x:Type local:TimersHostViewModel}"/>
</Window.Resources>

<Grid.RowDefinitions>
    <RowDefinition Height="21"/>
    <RowDefinition Height="*" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
    <ColumnDefinition Width="*"/>
    <ColumnDefinition Width="90"/>
</Grid.ColumnDefinitions>

<!--Main Menu-->
<Menu IsMainMenu="True" Style="{DynamicResource styleBanner}" Margin="0,0,0,1">
    <MenuItem Header="_Help" Style="{DynamicResource styleBanner}">
        <MenuItem Header="_About" Style="{DynamicResource styleBanner}"/>
    </MenuItem>
</Menu>

<!--Top Most Check Box-->
<CheckBox Content="Top Most" Grid.Column="1" Height="16" HorizontalAlignment="Left" Margin="11,2,0,0" Name="checkBox1" VerticalAlignment="Top" />

<!--Stopwatch & Countdown Tab Defintions-->
<TabControl Grid.Row="1" Grid.ColumnSpan="2" Style="{DynamicResource styleContentArea}">
    <TabItem Header="Stopwatch"/>
    <TabItem Header="Countdown"/>
</TabControl>

<!-- CONTEXT MENU -->
<Grid.ContextMenu>
  <ContextMenu Style="{DynamicResource styleBanner}" MenuItem.Click="OnMenuItemClick">
    <MenuItem Tag=".\Resources\Skins\BlackSkin.xaml" IsChecked="True">
        <MenuItem.Header>
        <Rectangle Width="120" Height="40" Fill="Black" />
        </MenuItem.Header>
    </MenuItem>
    <MenuItem Tag=".\Resources\Skins\GreenSkin.xaml">
        <MenuItem.Header>
        <Rectangle Width="120" Height="40" Fill="Green" />
        </MenuItem.Header>
    </MenuItem>
    <MenuItem Tag=".\Resources\Skins\BlueSkin.xaml">
        <MenuItem.Header>
        <Rectangle Width="120" Height="40" Fill="Blue" />
        </MenuItem.Header>
    </MenuItem>
  </ContextMenu>
</Grid.ContextMenu>
</Grid>
</Window>

Code Behind:

using System;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Diagnostics;
using System.Windows;
using System.Windows.Controls;

namespace TimersXP
{
public partial class TimersHost : Window
{
    public TimersHost()
    {
        try
        {
            InitializeComponent();
        }
        catch (Exception ex)
        {
            Debug.WriteLine("CTOR Exception: " + ex.Message);
        }

        // Load the default skin.
        Grid mainGrid = this.Content as Grid;
        MenuItem item = mainGrid.ContextMenu.Items[0] as MenuItem;
        this.ApplySkinFromMenuItem(item);

    }

    public void OnMenuItemClick(object sender, RoutedEventArgs e)
    {
        MenuItem item = e.OriginalSource as MenuItem;

        // Update the checked state of the menu items.
        //Grid mainGrid = this.Content as Grid;
        //foreach (MenuItem mi in mainGrid.ContextMenu.Items)
        //mi.IsChecked = mi == item;

        // Load the selected skin.
        this.ApplySkinFromMenuItem(item);
    }

    void ApplySkinFromMenuItem(MenuItem item)
    {
        // Get a relative path to the ResourceDictionary which
        // contains the selected skin.
        string skinDictPath = item.Tag as string;
        Uri skinDictUri = new Uri(skinDictPath, UriKind.Relative);

        // Tell the Application to load the skin resources.
        App app = Application.Current as App;
        app.ApplySkin(skinDictUri);
    }
}
}

ViewModel:

using System;
using System.Collections.Generic;
using System.Text;
using System.Windows;
using System.Windows.Controls;

namespace TimersXP
{
public class TimersHostViewModel
{
    public TimersHostViewModel()
    {
        // Load the default skin.
        Grid mainGrid = this.Content as Grid; <---- ERROR HERE
    }
    //public void TimersHostViewModel()
    //{
    //    // Load the default skin.
    //    Grid mainGrid = TimersHost.Content as Grid;
    //    MenuItem item = mainGrid.ContextMenu.Items[0] as MenuItem;
    //    //this.ApplySkinFromMenuItem(item);
    //}

    public void OnMenuItemClick(object sender, RoutedEventArgs e)
    {
        MenuItem item = e.OriginalSource as MenuItem;

        // Update the checked state of the menu items.
        //Grid mainGrid = this.Content as Grid;
        //foreach (MenuItem mi in mainGrid.ContextMenu.Items)
        //    mi.IsChecked = mi == item;

        // Load the selected skin.
        this.ApplySkinFromMenuItem(item);
    }

    void ApplySkinFromMenuItem(MenuItem item)
    {
        // Get a relative path to the ResourceDictionary which contains the selected skin.
        string skinDictPath = item.Tag as string;
        Uri skinDictUri = new Uri(skinDictPath, UriKind.Relative);

        // Tell the Application to load the skin resources.
        App app = Application.Current as App;
        app.ApplySkin(skinDictUri);
    }
}
}
1

1 Answers

1
votes

check out this link:

ContextMenu in MVVM

You need to bind your context menu items to a collection/property in your viewmodel. "This." will not work because that is the code behind and does not translate across to a view model.

Put this in view model:

class ContextItem : INotifyPropertyChanged
{
    public string Name;
    public ICommand Action;
    public Brush Icon;
}

ObservableCollection<ContextItem> Items {get;set;}

then in your view's context menu:

<Grid.ContextMenu>
    <ContextMenu ItemsSource="{Binding Items}/>

Anything you want to "pass" to the view needs to be a property/collection in your view model, you will never directly use a visual element object like a Gird/Context menu in you viewmodel. WPF handles the binding for you, which is the main benefit of WPF. Just make sure you implement INotifyPropertyChanged for the properties. I didn't to simplify the sample.

Now this does not mean there is never a case for code behind, but it should only involve visual elements, and not the data the visual elements bind to.

Hope this helps