Привязать WPF RelayCommand из DataTemplate к кнопке внутри UserControl - PullRequest
1 голос
/ 12 февраля 2020

Я долго искал, чтобы решить эту проблему с помощью RelayCommands, но не смог найти аналогичное решение.

Проблема в том, что у меня есть UserControl, и в этом UserConrol есть кнопка среди прочих ( btcNotKnown к концу кода):

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

В коде позади UserControl я определил DependencyProperty для предоставления команды из UserControl и, следовательно, привязка к кнопке в 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
}

Этот пользовательский контроль теперь используется внутри окна внутри шаблона данных, и вот здесь начинается проблема:

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

Окно (View) привязано к ViewModel (vmSession), который должен содержать код команды, так что для любого CustomControl, который будет использоваться DataTemplate, действительный Customcontrol может связать свою команду с действием в vmSession ViewModel (все CustomControls будут выполнять одно и то же действие при нажатии на конкретную кнопку, которую они размещают).

Определение команды в коде Позади:

#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

К сожалению, я просто могу получить обязательство работать таким образом, и я г Разобраться в подсказках о том, как связать команду кнопок не с Viewmodel позади UserControl, а с Viewmodel, в которой размещается ViewModel Usercontrols в манере MVVM, без рейлинга для делегатов или других обработчиков ...

Вот полное окно XAML (ContentControl с использованием DataTemplate находится в конце):

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

А здесь из Window ViewModel часть, где UserControl ViewModel предоставляется для 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 Ответ

0 голосов
/ 13 февраля 2020

Что ж, я должен действительно поблагодарить @ ΩmegaMan и других за то, что он попросил код для работы.

Я строю небольшой Игрушечный Проект, который действительно помог отладить проблему.

Что я изменил? На самом деле это было довольно просто: внутри DataTemplate мне просто нужно было использовать относительный источник, указывающий на окно, и использовать ссылку DataContext на свойство команды Window ViewModel (на которое ссылается текстовый текст окна).

В итоге я изменил:

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

Кому:

<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>
...