Search code examples
c#user-controlswinui-3winuicommunity-toolkit-mvvm

UserControl in WinUI 3: how to set the 'Command' property of a button?


Like this post, I want to create a user control consisting of multiple controls that will be used in several places in my application. Just like the other post, the buttons in the CommandBar will need to trigger events/commands specific to their usage. It all works great, except in some cases, I'm looking to use the Command property to trigger an IRelay command in the ViewModel of the page where the control is implemented instead of using the Click event. I hit a wall using the XAML Command property for the buttons.

I might be going about it completely wrong, but this is what I have and where I'm stuck.

The Page XAML for implementing the custom control binds to an IRelay command in the ViewModel. The ShowPropertiesCommand IRelay command in the user control's code behind is not found.

        <corecontrols:MyTreeView
            ...
            ShowPropertiesCommand="{x:Bind ViewModel.ShowPropertiesCommand}"
            TreeViewItemSource="{x:Bind ViewModel.Nodes, Mode=OneWay}" />

The UserControl XAML binds to the ShowPropertiesCommand IRelay command in the code behind.

    <UserControl x:Class="Core.UI.Controls.MyTreeView" ...>
        ...
            <CommandBar>
                ...
                <AppBarElementContainer>
                    <Button
                        x:Name="CB_NodeProperties"
                        Command="{x:Bind ShowPropertiesCommand}"
                        ... />
                </AppBarElementContainer>
            </CommandBar>
        ...
    </UserControl>

The code in the user control's code behind. The execute ExecuteShowPropertiesCommand is another place where I'm stuck. How can I have the implementation run the command in the ViewModel of the page where the user control is implemented?

        public MyTreeView()
        {
            this.InitializeComponent();
            ShowPropertiesCommand = new RelayCommand<object>(ExecuteShowPropertiesCommand);
        }
        public IRelayCommand ShowPropertiesCommand;

        private void ExecuteShowPropertiesCommand(object obj)
        {
            //WHAT TO PUT HERE??
        }

Solution

  • IIRC, the code in that post is using the Click event because it's doing something inside the UserControl.

    In your case, since you want to bind a command, you should create a DependencyProperty.

    Let me show you a simple example:

    SomeUserControl.xaml.cs

    using Microsoft.UI.Xaml;
    using Microsoft.UI.Xaml.Controls;
    using System.Windows.Input;
    
    namespace WinUIApp1;
    
    public sealed partial class SomeUserControl : UserControl
    {
        public static readonly DependencyProperty SomeCommandProperty = DependencyProperty.Register(
            nameof(SomeCommand),
            typeof(ICommand),
            typeof(SomeUserControl),
            new PropertyMetadata(default));
    
        public SomeUserControl()
        {
            InitializeComponent();
        }
    
        public ICommand SomeCommand
        {
            get => (ICommand)GetValue(SomeCommandProperty);
            set => SetValue(SomeCommandProperty, value);
        }
    }
    

    SomeUserControl.xaml

    <UserControl
        x:Class="WinUIApp1.SomeUserControl"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:local="using:WinUIApp1"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        mc:Ignorable="d">
        <CommandBar>
            <AppBarElementContainer>
                <Button
                    Command="{x:Bind SomeCommand, Mode=OneWay}"
                    Content="Click" />
            </AppBarElementContainer>
        </CommandBar>
    </UserControl>
    

    Then you can bind a command like this:

    SomePage.xaml

    <Page
        x:Class="WinUIApp1.SomePage"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:local="using:WinUIApp1"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        Background="{ThemeResource ApplicationPageBackgroundThemeBrush}"
        mc:Ignorable="d">
    
        <local:SomeUserControl SomeCommand="{x:Bind ViewModel.DoSomeTaskCommand}" />
    
    </Page>
    

    SomePage.xaml.cs

    public sealed partial class SomePage : Page
    {
        public Shell()
        {
            InitializeComponent();
        }
    
        public SomeViewModel ViewModel { get; } = new();
    }
    

    SomePageViewModel.cs

    public partial class SomeViewModel : ObservableObject
    {
        // The CommunityToolkit.Mvvm's source generator will generate 
        // a 'DoSomeTaskCommand' IRelayCommand for you.
        [RelayCommand]
        private void DoSomeTask()
        {
            // Do some task here...
        }
    }
    

    NOTE

    ObservableObject and ObservableProperty come from the CommunityToolkit.Mvvm NuGet package.