Search code examples
c#wpfmvvmribbonribbon-control

How to trigger a method in a ViewModel from a WPF Ribbon button


I'm fairly inexperienced with WPF, Ribbon menus and C#, as well as the MVVM pattern. So please excuse any stupidity.

My problem is that I have a WPF app thai I am attempting to have a MainView which contains the ribbon component as well as a ContentControl displaying a current view. How do I trigger a method in the current view from the MainView ribbon? It does seem to have some awareness of the view as it is getting the "Name" paramter from it, but throws an error if I attempt to bind to anything else (as that thing isn't in the interface I used to create the list of views).

Here is the code that I hope is enough to give a sense of my issue.

MainWindowView.xaml

<RibbonWindow x:Class="LIA.MainWindowView"
              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" 
              mc:Ignorable="d"              
              xmlns:local="clr-namespace:LIA" d:DataContext="{d:DesignInstance Type=local:MainWindowViewModel}"
              Title="LIA"
              Width="Auto" Height="480">

    <RibbonWindow.Resources>
        <!-- views and models for the app -->
        <DataTemplate DataType="{x:Type local:SampleViewModel}">
            <local:SampleView />
        </DataTemplate>
    </RibbonWindow.Resources>

    <Grid x:Name="LayoutRoot">
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto" />
            <RowDefinition Height="*"/>
        </Grid.RowDefinitions>
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="*"/>
        </Grid.ColumnDefinitions>
        <Ribbon x:Name="Ribbon" Title="Ribbon Title" HorizontalAlignment="Center" Width="{Binding ElementName=LayoutRoot, Path=ActualWidth}">
            <Ribbon.HelpPaneContent>
                <RibbonButton SmallImageSource="Images/smallicon.png" />
            </Ribbon.HelpPaneContent>
            <Ribbon.QuickAccessToolBar>
                <RibbonQuickAccessToolBar>
                    <RibbonButton x:Name="QATButton1"
                                  SmallImageSource="Images/SmallIcon.png" />
                    <RibbonButton x:Name="QATButton2"
                                  SmallImageSource="Images/SmallIcon.png" />
                </RibbonQuickAccessToolBar>
            </Ribbon.QuickAccessToolBar>
            <Ribbon.ApplicationMenu>
                <RibbonApplicationMenu SmallImageSource="Images\SmallIcon.png">
                    <RibbonApplicationMenuItem Header="Hello _Ribbon"
                                               Width="Auto"
                                               x:Name="MenuItem1"
                                               ImageSource="Images\LargeIcon.png" />
                </RibbonApplicationMenu>
            </Ribbon.ApplicationMenu>
                        <RibbonTab x:Name="SampleTab"
                       Header="Aname">
                            <RibbonGroup x:Name="SampleTabGroup1"
                             Header="A header">
                                <RibbonButton x:Name="aLargeButton"
                                              
                                  LargeImageSource="Images\LargeIcon.png"
                                  Label="Button5"
                                  />
                            </RibbonGroup>
                        </RibbonTab>
                    
            <RibbonTab x:Name="tab"
                       Header="Sample Tab">
                <RibbonGroup x:Name="Group1"
                             Header="">
                    <ItemsControl ItemsSource="{Binding PageViewModels}">                        
                            <ItemsControl.ItemTemplate>
                            <DataTemplate>
                                <RibbonButton
                                    x:Name="Button1"                                    
                                  LargeImageSource="Images\LargeIcon.png"
                                  Label="{Binding Name}" />
                            </DataTemplate>
                        </ItemsControl.ItemTemplate>
                    </ItemsControl>
                    
                </RibbonGroup>
            </RibbonTab>
                        
        </Ribbon>

        <ContentControl Content="{Binding CurrentPageViewModel}" Grid.Row="1" />

    </Grid>

</RibbonWindow>

MainWIndowView.xaml.cs:

using System.Windows.Controls.Ribbon;

namespace LIA
{
    /// <summary>
    /// Interaction logic for MainWindow.xaml
    /// </summary>
    public partial class MainWindowView : RibbonWindow
    {
        public MainWindowView()
        {
            InitializeComponent();
            this.DataContext = new MainWindowViewModel();
        }

    }
}

MainWindowViewModel.cs:

using System.Collections.Generic;
using System.Linq;
using System.Windows.Input;

namespace LIA
{
    class MainWindowViewModel : ObservableObject
    {
        #region MainWindowViewModel Fields

        private ICommand _changePageCommand;
        private IPageViewModel _currentPageViewModel;
        private List<IPageViewModel> _pageViewModels;

        #endregion MainWindowViewModel Fields

        public MainWindowViewModel()
        {
            //Add pages
            PageViewModels.Add(new SampleViewModel());
            CurrentPageViewModel = PageViewModels[0];
        }

        #region MainWindowViewModel Properties

        public List<IPageViewModel> PageViewModels
        {
            get
            {
                if (_pageViewModels == null)
                    _pageViewModels = new List<IPageViewModel>();

                return _pageViewModels;
            }
        }

        public IPageViewModel CurrentPageViewModel
        {
            get
            {
                return _currentPageViewModel;
            }
            set
            {
                if (_currentPageViewModel != value)
                {
                    _currentPageViewModel = value;
                    OnPropertyChanged("CurrentPageViewModel");
                }
            }
        }

        #endregion MainWindowViewModel Properties

        #region MainWindowViewModel Commands

        public ICommand ChangePageCommand
        {
            get
            {
                if (_changePageCommand == null)
                {
                    _changePageCommand = new RelayCommand(
                        p => ChangeViewModel((IPageViewModel)p),
                        p => p is IPageViewModel
                        );
                }
                System.Diagnostics.Debug.WriteLine("page change command");
                return _changePageCommand;
            }
        }



        #endregion MainWindowViewModel Commands


        #region MaiWindowViewModel Methods

        private void ChangeViewModel(IPageViewModel viewModel)
        {
            if (!PageViewModels.Contains(viewModel))
            {
                PageViewModels.Add(viewModel);

                CurrentPageViewModel = PageViewModels.FirstOrDefault(vm => vm == viewModel);
            }
        }

        #endregion MaiWindowViewModel Methods
    }
}

Interface:

using System.Collections.Generic;
using System.Windows.Input;

namespace LIA
{
    public interface IPageViewModel
    {
        string Name { get; }
    }
}

SampleViewModel.cs:

using System;
using System.Collections.Generic;
using System.Text.RegularExpressions;
using System.Collections.ObjectModel;
using System.Data.OleDb;
using System.Drawing;
using System.Drawing.Printing;
using System.Linq;
using System.Windows.Input;
using ZXing;
using ZXing.Datamatrix;

namespace LIA
{
    class SampleViewModel : ObservableObject, IPageViewModel
    {
        #region SampleViewModel Fields

        private ICommand _generateLabel;

        #endregion SampleViewModel Fields

        public SampleViewModel()
        {
            
        }


        #region SampleViewModel Properties

        public string Name
        {
            get { return "Sample"; }
        }

        #endregion SampleViewModel Properties

        #region SampleViewModel Commands

        public ICommand GenerateLabelCommand
        {
            get
            {
                if (_generateLabel == null)
                {
                    _generateLabel = new RelayCommand(
                        param => GenerateLabel(),                        
                        );
                }
                return _generateLabel;
            }
        }

        #endregion SampleViewModel Commands

        #region SampleViewModel Methods

        private void GenerateLabel()
        {
            //do the stuff I need to generate a label
        }

        #endregion SampleViewModel Methods
    }
}

I haven't included the SampleView.xaml or SampleView.xaml.cs as that seems to be working ok and I can trigger the method when the button is on that view.

and heres the output: Menus with button showing correct name

Been going crazy looking for solutions on this, which probably means there's a fundamental concept here I don't get yet.


Solution

  • If I understand this correctly from your code: in your RibbonGroup named Group1, you want to have a Button for each item in PageViewModels, which will execute some code from that IPageViewModel item when clicked, correct?

    In your example above, adding Command="{Binding GenerateLabelCommand}" to Button1 should link it to the command in SampleViewModel. It shouldn't matter that the collection it's a part of is defined as a List<IPageViewModel>. If that doesn't work, more debugging details are needed.

    That said, the above will only work for SampleViewModel, unless every IPageViewModel has a member named GenerateLabelCommand. If you want this to work for all IPageViewModel implementations, you should add an ICommand member to the interface and then bind explicitly to the interface implementation as explained in this question: WPF databinding to interface and not actual object - casting possible?