как получить доступ к свойствам зависимостей viewModel из унаследованного пользовательского элемента управления? - PullRequest
0 голосов
/ 24 апреля 2020

Я боролся в демонстрационном проекте WPF. Я должен признать, что у меня есть много ограничений, которые усложняют код.

Основной элемент - это элемент управления, который наследуется от UserControl. Я бы хотел, чтобы его код был как можно более легким. Кроме того, я хотел бы иметь его XAML в ControlTemplate. Его код C# должен быть в выделенной ViewModel (этот пример для огромного проекта, и выделенная viewModel может помочь, сгруппировав все viewmodel. Но в любом случае, скажем, это обязательно). И последнее, но не менее важное: я хотел бы привязать 2 свойства этого элемента управления к внешним свойствам.

Вот мой файл MainWindow.xaml:

<Window x:Class="ViewModel_defined_in_ControlTemplate.MainWindow"
        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:ViewModel_defined_in_ControlTemplate"
        mc:Ignorable="d"
        Title="MainWindow" Height="450" Width="800">
    <Window.Resources>
        <ResourceDictionary>
            <ResourceDictionary.MergedDictionaries>
                <ResourceDictionary Source="MyDictionary.xaml"/>
            </ResourceDictionary.MergedDictionaries>
        </ResourceDictionary>
    </Window.Resources>
    <Grid>
        <StackPanel>
            <local:MyUserControl Template="{StaticResource TextBoxTemplate}"
                                 NomPersonne="sg"/>
            <Button Content="Click me!" Command="{Binding ElementName=MyViewModel,Path=ChangeTextBoxContent}" Width="100" HorizontalAlignment="Left"/>
        </StackPanel>
    </Grid>
</Window>

Кнопка просто меняет значение свойство зависимости NomPersonne (см. ниже). MyDictionary.xaml содержит ControlTemplate:

<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
                    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
                    xmlns:local="clr-namespace:ViewModel_defined_in_ControlTemplate">

    <ControlTemplate x:Key="TextBoxTemplate" TargetType="{x:Type local:MyUserControl}">
        <Grid>
            <Grid.DataContext>
                <local:MyViewModel/>
            </Grid.DataContext>
            <TextBox Width="50" HorizontalAlignment="Left" Text="{TemplateBinding NomPersonne}"/>
        </Grid>


    </ControlTemplate>    
</ResourceDictionary>

Я не знаю, где разместить свое свойство зависимости и как получить к нему доступ. Я попытался поместить его в MyUserControl:

namespace ViewModel_defined_in_ControlTemplate
{
    public partial class MyUserControl : UserControl
    {



        public string NomPersonne
        {
            get { return (string)GetValue(NomPersonneProperty); }
            set { SetValue(NomPersonneProperty, value); }
        }

        public static readonly DependencyProperty NomPersonneProperty =
            DependencyProperty.Register("NomPersonne", typeof(string), typeof(MyUserControl), new PropertyMetadata(""));

    }
}

, и теперь он доступен из XAML MyUserCOntrol, но тогда я не знаю, как получить к нему доступ, чтобы команда кнопки изменила свойство:

namespace ViewModel_defined_in_ControlTemplate
{
    public class MyViewModel : ViewModelBase
    {

        public RelayCommand ChangeTextBoxContent = new RelayCommand(() => 
        { 
            //...
        }, () => true);

    }

}

Я бы предпочел иметь свойство зависимости в viewmodel, но как в этом случае получить доступ к входу в XAML MyUserControl, в MainWindow?

спасибо.

Ответы [ 2 ]

1 голос
/ 24 апреля 2020

Вы должны добавить свойство source к модели представления, связать свойство target UserControl с этим и обновить свойство источника в модели представления:

<local:MyUserControl Template="{StaticResource TextBoxTemplate}" 
                     NomPersonne="{Binding Name}"/>

Просмотр модели:

public class MyViewModel : ViewModelBase
{
    public RelayCommand ChangeTextBoxContent = new RelayCommand(() =>
    {
        Name = "...":
    }, () => true);

    private string _name;

    public string Name
    {
        get { return _name; }
        set { _name = value; OnPropertyChanged(); }
    }

    ...
}

Вы также должны установить DataContext в окне, а не в ResourceDictionary:

<Window x:Class="ViewModel_defined_in_ControlTemplate.MainWindow"
    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:ViewModel_defined_in_ControlTemplate"
    mc:Ignorable="d"
    Title="MainWindow" Height="450" Width="800">
    <Window.Resources>
        <ResourceDictionary>
            <ResourceDictionary.MergedDictionaries>
                <ResourceDictionary Source="MyDictionary.xaml"/>
            </ResourceDictionary.MergedDictionaries>
        </ResourceDictionary>
    </Window.Resources>
    <Window.DataContext>
        <local:MyViewModel/>
    </Window.DataContext>
    <Grid>
        <StackPanel>
            <local:MyUserControl Template="{StaticResource TextBoxTemplate}"
                                 NomPersonne="{Binding Name}"/>
            <Button Content="Click me!" Command="{Binding ChangeTextBoxContent}" Width="100" HorizontalAlignment="Left"/>
        </StackPanel>
    </Grid>
</Window>

ResourceDictionary должен определять только шаблон:

    <ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
                xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
                xmlns:local="clr-namespace:ViewModel_defined_in_ControlTemplate">

        <ControlTemplate x:Key="TextBoxTemplate" TargetType="{x:Type local:MyUserControl}">
            <Grid>
                <TextBox Width="50" HorizontalAlignment="Left" 
                         Text="{TemplateBinding NomPersonne}"/>
            </Grid>
        </ControlTemplate>
    </ResourceDictionary>
0 голосов
/ 24 апреля 2020

Это очень плохая идея - дать UserControl ViewModel. Вы только что наткнулись на одну из многих проблем этого подхода. Невозможно определить DependecyProperty в ViewModel, единственным способом является выделение кода.

Чтобы синхронизировать данные между выделенным кодом и ViewModel, необходимо подписаться на PropertyChanged из ViewModel в выделенном коде и каждый раз, когда значение изменяется в ViewModel, обновляйте соответствующие свойства DependencyProperties в выделенном коде. Это должно работать и наоборот. Когда изменяется свойство DependencyProperty, вы должны обновить его в ViewModel. Достигнуть этого не невозможно, но это действительно уродливо (поверьте мне, я это сделал; никогда больше не буду).

Другая проблема - установка DataContext для UserControl (в коде позади или XAML) в ViewModel. Если вы установите его в UserControl напрямую, привязки не будут работать. Обходной путь - установить DataContext первого дочернего элемента UserControl (опять же, не делайте этого).

UserControl с ViewModel - действительно плохая идея. Но это не значит, что ваш код должен содержать весь код. Вы всегда можете извлечь методы, выполняющие некоторые сложные логики c, в их отдельные классы. Метод stati c может быть вызван где угодно, даже с выделенным кодом.

...