1
votes

I searched quite a while to solve this issue with RelayCommands but could not find a similar solution.

The issue is that I have a UserControl, and in this UserConrol is a button among others (btcNotKnown towards the end of the code):

<UserControl x:Class        = "Vokabelizer.Views.viewLearnSpeak"
         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" 
         xmlns:local    = "clr-namespace:Vokabelizer.Views"
         xmlns:conv     = "clr-namespace:Vokabelizer.Global.Converter"
         xmlns:ccont    = "clr-namespace:Vokabelizer.Controls;assembly=Vokabelizer.Controls"
         mc:Ignorable   = "d" 
         d:DesignHeight = "300"
         d:DesignWidth  = "800"
         Height         = "300"
         x:Name         = "root">

<UserControl.Resources>
    ...
</UserControl.Resources>

<Grid Grid.Column="1" VerticalAlignment="Stretch" HorizontalAlignment="Stretch" Margin="0">

    <Grid.RowDefinitions>
        ...
    </Grid.RowDefinitions>

    <!-- The question to answer -->
    <Border Grid.Column         = "0"
            Grid.Row            = "0"
            Padding             = "5"
            HorizontalAlignment = "Stretch"
            VerticalAlignment   = "Stretch">

        <TextBlock Text                 = "{Binding Path=LessonNative}"
                   Style                = "{StaticResource UIText}"/>

    </Border>

    <!-- Validation content -->
    <Border Grid.Column         = "0" 
            Grid.Row            = "1"
            Padding             = "5">

        <ToggleButton x:Name        = "QnAToggle"
                      Command       = "{Binding Path=cmdValidateOK}"
                      IsThreeState  = "False">

            <ToggleButton.Style>
                <Style TargetType="ToggleButton" BasedOn="{StaticResource ValidateToggle}">
                    <Style.Triggers>
                        <Trigger Property="IsChecked" Value="False">
                            <Setter Property="Content">
                                <Setter.Value>
                                    <TextBlock Text     = "?"
                                               Style    = "{StaticResource UIText}" />
                                </Setter.Value>
                            </Setter>
                        </Trigger>
                        <Trigger Property="IsChecked" Value="True">
                            <Setter Property="Content">
                                <Setter.Value>
                                    <TextBlock Text     = "{Binding Path=LessonForeign}"
                                               Style    = "{StaticResource UIText}" />
                                </Setter.Value>
                            </Setter>
                        </Trigger>
                    </Style.Triggers>
                </Style>
            </ToggleButton.Style>

        </ToggleButton>

    </Border>

    <!-- Result Evaluation buttons -->
    <Border Grid.Column         = "0"
            Grid.Row            = "2">

        <Grid>

            <Grid.ColumnDefinitions>
                ...
            </Grid.ColumnDefinitions>

            <ccont:vokButton x:Name         = "btcNotKnown"
                             Command        = "{Binding Command, ElementName=root}"
                             Grid.Column    = "0"
                             Grid.Row       = "0"
                             Content        = "Not Known"
                             Corner         = "5"
                             Style          = "{StaticResource ValidateNotKnown}"
                             Visibility     = "{Binding ElementName=QnAToggle, Path=IsChecked, Converter={StaticResource BoolToVisibilityConverter}, ConverterParameter=hidden}" />

        </Grid>

    </Border>

</Grid>

In the code behind of the UserControl I defined a DependencyProperty to expose the command from the UserControl and hence bind to the Button in the UserControls xaml:

public partial class viewLearnSpeak : UserControl
{
    public viewLearnSpeak()
    {
        InitializeComponent();
    }

    #region DepProp: Command

    public static readonly DependencyProperty CommandProperty = DependencyProperty.Register("Command", typeof(ICommand), typeof(viewLearnSpeak), new PropertyMetadata(null));

    public ICommand Command
    {
        get => (ICommand)GetValue(CommandProperty);
        set => SetValue(CommandProperty, value);
    }

    #endregion
}

This Usercontrol is now used inside a Window inside a DataTemplate, and here is where the trouble starts:

<DataTemplate DataType="{x:Type vm:vmLearnSpeak}">
    <local:viewLearnSpeak Command="{Binding cmdVMNotKnown, ElementName=root}" />
</DataTemplate>

The Window (View) is bound to a ViewModel (vmSession), which should host the command code, so that for any CustomControl that will be used by the DataTemplate, the valid Customcontrol can bind its command to an action in the vmSession ViewModel (all CustomControls will do the same action when clicked on a particular button they host).

Command definition in code Behind:

#region Command: Not Known

private ICommand _cmdVMNotKnown;
public ICommand cmdVMNotKnown
{
    get
    {
        if (_cmdVMNotKnown == null)
        {
            _cmdVMNotKnown = new RelayCommand(
                param => this.doVMNotKnown(),
                param => { return true; }
            );
        }
        return _cmdVMNotKnown;
    }
}

protected void doVMNotKnown()
{
}

#endregion

Unfortunately I just can get the binding to work this way and I am running out of clues on how to bind the buttons command not to the Viewmodel behind the UserControl, but to the Viewmodel hosting the Usercontrols ViewModel in a MVVM manner without reyling on delegates or other handlers...

Here is the full window XAML (the ContentControl using the DataTemplate is at the end):

<Window x:Class         = "Vokabelizer.Views.wndSession"
    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:mc        = "http://schemas.openxmlformats.org/markup-compatibility/2006"
    xmlns:local     = "clr-namespace:Vokabelizer.Views"
    xmlns:res       = "clr-namespace:Vokabelizer.Resources"
    xmlns:vm        = "clr-namespace:Vokabelizer.ViewModels"
    mc:Ignorable    = "d"
    Title           = "{x:Static res:Strings.wndLearnTitle}" 
    Height          = "410"
    Width           = "800"
    ResizeMode      = "NoResize"
    x:Name          = "root">

<Window.DataContext>
    <vm:vmSession />
</Window.DataContext>

<Window.Resources>
    <DataTemplate DataType="{x:Type vm:vmLearnSpeak}">
        <local:viewLearnSpeak Command="{Binding cmdVMNotKnown, ElementName=root}" />
    </DataTemplate>
    <DataTemplate DataType="{x:Type vm:vmLearnWrite}">
        <local:viewLearnWrite />
    </DataTemplate>
    <DataTemplate DataType="{x:Type vm:vmLearnListen}">
        <local:viewLearnListen />
    </DataTemplate>
</Window.Resources>

<Grid Margin="10">

    <Grid.ColumnDefinitions>
        <ColumnDefinition Width = "Auto" />
        <ColumnDefinition Width = "*" />
        <ColumnDefinition Width = "Auto" />
    </Grid.ColumnDefinitions>

    <Grid.RowDefinitions>
        <RowDefinition Height = "50"  />
        <RowDefinition Height = "300" />
    </Grid.RowDefinitions>

    <!-- Title description -->
    <TextBlock x:Name="txtModeDescription"  Text="{Binding LearnTitle}"
               HorizontalAlignment="Left"   VerticalAlignment="Center"
               Grid.Column="0"              Grid.ColumnSpan="1" 
               Grid.Row="0"                 Grid.RowSpan="1"
               Margin="0, 0, 30, 0"        Foreground="DarkGray"
               FontWeight="Bold" FontSize="18" FontFamily="Arial Black" />

    <!-- Progress Slider -->
    <Slider x:Name="sliderProgress"
            HorizontalAlignment="Stretch"   VerticalAlignment="Center"
            Grid.Column="1"                 Grid.ColumnSpan="1" 
            Grid.Row="0"                    Grid.RowSpan="1"
            Minimum="0"                     Maximum="11" />

    <!-- Title status -->
    <TextBlock x:Name="txtBatchAdvancement" Text="{Binding LearnProgressBatch}"
               HorizontalAlignment  = "Right"   VerticalAlignment   = "Center"
               Grid.Column          = "3"       Grid.ColumnSpan     = "1" 
               Grid.Row             = "0"       Grid.RowSpan        = "1"
               Margin               = "10, 0, 0, 0"/>

    <Border Grid.Row            = "1"
            Grid.Column         = "0"
            HorizontalAlignment = "Center"
            VerticalAlignment   = "Center"
            Padding             = "0">

        <Image Source               = "Demo.png"
               Height               = "300"
               Width                = "300"
               HorizontalAlignment  = "Center"
               VerticalAlignment    = "Center" />

    </Border>

    <ContentControl Content     = "{Binding learningViewModel}"
                    Grid.Row    = "1"   Grid.RowSpan="1"
                    Grid.Column = "1"   Grid.ColumnSpan="2"
                    Height      = "300"
                    Margin      = "20, 0, 20, 0"/>

</Grid>

And here the from the Window ViewModel the part where the UserControl ViewModel is exposed for the DataTemplate:

private ILearnVM _learningViewModel;
/// <summary>
/// The Learning Provider View Model
/// </summary>
public ILearnVM learningViewModel 
{
    get => this._learningViewModel;
    private set
    {
        this._learningViewModel = value;

        this.OnPropertyChanged(nameof(this.learningViewModel));
    }
}
1
What is xmlns:ccont = "clr-namespace:Vokabelizer.Controls;assembly=Vokabelizer.Controls" ? I am trying to recreate your issue.Decoder94
I generally avoid having VMs for user controls because dependency properties are the backing store for data. You need to distill this issue into a test project so we can attempt to debug it. While making the example you may glean why this is failing in the main project.ΩmegaMan
Can please post only the relevant code?BionicCode
@Decoder94: It is a control library. Basically ccont:vokButton isjust a custom styled button, hence that could be replaced by a button minus the style inputs.Patrick
I built a small toy project (VS2019) which shows the issue here: link on MediafirePatrick

1 Answers

0
votes

Well I must really thank @ΩmegaMan and other requesting some code to work.

I build a small Toy Project which really helped debug the issue.

What did I change? Actually it was quite simple, inside the DataTemplate, I just needed to use a relative source pointing to the Window and use a DataContext reference to the command property of the Window ViewModel (which is referenced in the Window Datacontext).

I ended up changing:

<Window.Resources>
    <DataTemplate DataType="{x:Type vm:vmLearnSpeak}">
        <local:viewLearnSpeak Command="{Binding cmdVMNotKnown, ElementName=root}" />
    </DataTemplate>
    <DataTemplate DataType="{x:Type vm:vmLearnWrite}">
        <local:viewLearnWrite />
    </DataTemplate>
    <DataTemplate DataType="{x:Type vm:vmLearnListen}">
        <local:viewLearnListen />
    </DataTemplate>
</Window.Resources>

To:

<Window.Resources>
    <DataTemplate DataType="{x:Type vm:vmLearnSpeak}">
        <local:viewLearnSpeak Command="{Binding DataContext.cmdVMNotKnown, RelativeSource={RelativeSource AncestorType=local:wndSession}}" />
    </DataTemplate>
    <DataTemplate DataType="{x:Type vm:vmLearnWrite}">
        <local:viewLearnWrite />
    </DataTemplate>
    <DataTemplate DataType="{x:Type vm:vmLearnListen}">
        <local:viewLearnListen />
    </DataTemplate>
</Window.Resources>