Пользовательские элементы управления для создания определенных моделей - PullRequest
0 голосов
/ 04 мая 2019

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

Model:

public class FooModel
{
    public string Whatever { get; set; }
}

ViewModel:

public class FooViewModel
{
    public FooModel Foo { get; set; }

    public ICommand CreateCommand { get; set; } = new AnotherCommandImplementation<FooModel>(model =>
    {
        // model is null! :(
    });
}

UserControl:

<UserControl>
    <UserControl.DataContext>
        <local:FooViewModel />
    </UserControl.DataContext>
    <StackPanel Orientation="Horizontal">
        <TextBox Text="{Binding Foo.Whatever}" Height="23" Width="120"/>
        <Button CommandParameter="{Binding Foo}" Command="{Binding CreateCommand}" Width="80" Content="Create"/>
    </StackPanel>
</UserControl>

Почему Foo имеет значение NULL и как это исправить?

ОБНОВЛЕНИЕ

По запросу, воттекущая попытка DataTemplate:

App.xaml:

<Application>
    <Application.Resources>
        <DataTemplate DataType="{x:Type vms:KeyboardActionViewModel}">
            <ctrs:KeyboardActionControl />
        </DataTemplate>
    </Application.Resources>
</Application>

Окно:

<Window>
    <Window.DataContext>
        <vms:ActionExecutorViewModel />
    </Window.DataContext>
    <StackPanel>
        <CheckBox IsChecked="{Binding Enabled}" Content="Enabled" />
        <UserControl Content="{Binding Action}" />
    </StackPanel>
</Window>

ViewModel:

public class ActionExecutorViewModel : ViewModel<ActionExecutor>
{
    private Boolean enabled;
    private ActionViewModel action;

    public ActionExecutorViewModel()
    {
        Action = new KeyboardActionViewModel(); // Test
    }

    public ActionViewModel Action
    {
        get => action;
        set => AssignAndRaiseEventOnPropertyChange(ref action, value);
    }

    public Boolean Enabled
    {
        get => enabled;
        set => AssignAndRaiseEventOnPropertyChange(ref enabled, value);
    }

    public override ActionExecutor BuildModel()
    {
        var executor = new ActionExecutor();

        executor.Action = action.BuildModel();

        return executor;
    }
}

KeyboardActionControl:

<UserControl>
    <Label Background="Aqua">Asadsadsad</Label>
</UserControl>

ActionViewModel - абстрактный класс, в котором наследуется KeyboardActionViewModel.

Ответы [ 2 ]

3 голосов
/ 04 мая 2019

Как отмечает Сережа, ваша проксимальная проблема в том, что Foo равно нулю. Вы никогда не создавали его, поэтому его там нет. Вероятно, он должен быть создан FooViewModel, но возможно, что создатель FooViewModel также должен создать Foo. Не зная семантики, я не могу быть уверен. Представление не должно быть абсолютно ответственным за создание одного из них.

Но есть плохие предположения, встроенные в то, что вы делаете. Давайте исправим это и приведем вас на правильный путь.

ViewModelBase реализует INotifyPropertyChanged. Примеров предостаточно. Представленные ниже фрагменты кода XAML предназначены для того, чтобы быть частичными: есть биты пользовательского интерфейса, которые не проиллюстрированы, поскольку они не должны представлять никаких трудностей.

public class MainViewModel : ViewModelBase
{
    public ActionExecutorCollectionViewModel ActionExecutors { /* INPC stuff */ }
        //  ViewModels create their own children. 
        = new ActionExecutorCollectionViewModel();
}

public class ActionExecutorCollectionViewModel : ViewModelBase
{
    public ObservableCollection<ActionExecutor> Items { /* INPC stuff */ }
    public ActionExecutor NewActionExecutor { /* INPC stuff */ }

    // Create new ActionExecutor and assign to NewActionExecutor 
    public ICommand CreateActionExecutor { /* ... */ }

    // Add NewActionExecutor to Items and set NewActionExecutor to null 
    public ICommand SaveActionExecutor { /* ... */ }
}

Напишите неявный DataTemplate для каждого из вышеперечисленных. В DataTemplate MainViewModel есть что-то вроде этого:

<ContentControl Content="{Binding ActionExecutors}" />

Это отображает ActionExecutorsViewModel с неявным DataTemplate, который содержит что-то вроде этого, среди прочего:

<Button
    Command="{Binding CreateActionExecutor}"
    Content="Create"
    />
<Button
    Command="{Binding SaveActionExecutor}"
    Content="Save"
    />
<ContentControl
    Content="{Binding NewActionExecutor}"
    />

ActionExecutor нужна какая-то фабрика необработанных классов для создания собственного Action. У вас есть два типа действий сейчас. Я бы посоветовал не сойти с ума в данный момент, пытаясь написать идеальную архитектуру для добавления новых в будущем. Вместо этого я бы предложил предоставить ActionExecutor открытую для чтения коллекцию опций типа действия, возможно, значений из перечисления: public ActionType { Mouse, Keyboard } и свойства public ActionType ActionType. Когда ActionType изменится, создайте новое действие нового типа и назначьте его свойству Action. Установщик ActionType должен вызывать защищенный метод, который делает это. Есть и другие, более умные варианты для этого, но вышеупомянутая конструкция достаточно проста в обслуживании и хорошо зарекомендовала себя в тысячах производственных приложений.

В неявном DataTemplate ActionExecutor у вас будет комбинированный список, который позволяет пользователю выбрать тип действия из коллекции ActionTypes. Его свойство SelectedItem привязано к ActionType. Вот как создаются действия.

DataExemplate ActionExecutor содержит что-то вроде этого:

<CheckBox Content="Enabled" IsChecked="{Binding Enabled}" />
<ComboBox ItemsSource="{Binding ActionTypes}" SelectedItem="{Binding ActionType}" />
<ContentControl Content="{Binding Action}" />
  1. Все модели представления ниже MainViewModel создаются их непосредственными родительскими моделями представления, никогда и никогда никогда не выполняется представлением . Думайте о дереве представления как о каркасе или структуре приложения. Представления просто отображают биты по мере необходимости. Viewmodels должны общаться друг с другом; Просмотры не . Они просто отражают и провоцируют изменения состояния в своих моделях представления. Окно может создать свою модель представления в своем конструкторе или в XAML как <Window.DataContext><local:MainViewModel /></Window.DataContext>. Либо хорошо, но выполнение этого в конструкторе позволяет вызвать конструктор с параметрами.

  2. Таким образом, за этим единственным исключением UserControl всегда получает свой DataContext из контекста, никогда , создавая его , Это практический, а не идеологический вопрос: он делает написание и сопровождение приложения намного проще, чем альтернатива. Многие неприятные проблемы исчезают, если вы следуете этому правилу. UserControl в хорошо спроектированном приложении WPF редко определяет свойства зависимостей. Целью UserControl является отображение модели представления. Другие типы элементов управления будут определять обширные, роскошные, сверкающие наборы свойств зависимостей. Не UserControls.

  3. Вы можете написать UserControls и поместить их в DataTemplates или просто написать DataTemplates. Я считаю, что написание UserControls - хорошая идея. DataTemplate, содержащий UserControl, выглядит ТОЧНО ТАКИМ ОБРАЗОМ:

    <DataTemplate DataType="{x:Type ActionExecutor}">
        <local:ActionExecutorUserControl />
    </DataTemplate>
    
  4. DataContext="{Binding SomeProperty}" по сути всегда неправильно. Это «запах кода», который указывает на то, что кто-то еще не очень хорошо понимает XAML.

если сомЧасть вышеперечисленного не имеет смысла для вас, я буду рад помочь вам заполнить этот пробел в ваших знаниях.Если вы считаете, что какая-то его часть противоречит вашим требованиям, вы вполне можете ошибиться.Тем не менее, вы несете ответственность за полное понимание и кодификацию своих собственных требований, а также за четкое информирование об этих требованиях.

UPDATE

Неявные шаблоны данных

Неявный шаблон данных представляет собой 1) шаблон данных, определенный как ресурс в доступном ResourceDictionary, с 2) атрибут DataType, указывающий, какой из ваших классовВы хотите отобразить с ним.

App.xaml

<Application.Resources>
    <DataTemplate DataType="{x:Type ActionExecutorCollectionViewModel}">
        <local:ActionExecutorCollectionUserControl />
    </DataTemplate>
    <DataTemplate DataType="{x:Type ActionExecutor}">
        <local:ActionExecutorUserControl />
    </DataTemplate>
    <DataTemplate DataType="{x:Type MouseAction}">
        <local:MouseActionUserControl />
    </DataTemplate>
    <!-- And so on and so forth. -->
</Application.Resources>

MainWindow.xaml

DataContext MainWindow - это ваш MainViewModel, который я частично определил выше.

<Grid>
    <!-- 
    MainViewModel.ActionExecutors is of type ActionExecutorCollectionViewModel.
    If you defined an implicit datatemplate for that class in some ResourceDictionary 
    that's in scope here (e.g., App.xaml), this UserControl will automatically 
    use that datatemplate. 
    -->
    <UserControl Content="{Binding ActionExecutors}" />
</Grid>

ActionExecutorUserControl.xaml

<StackPanel>
    <StackPanel Orientation="Horizontal">
        <Label>Interval</Label>
        <TextBox Text="{Binding Interval}" />
    </StackPanel>
    <CheckBox IsChecked="{Binding Enabled}">Enabled</CheckBox>

    <!-- 
    If you have implicit datatemplates defined for all your action types,
    the framework will automatically give this UserControl the correct template
    for whatever actual type of action the Action property refers to.

    This is where we begin to see the real value of implicit datatemplates. 
    -->
    <UserControl Content="{Binding Action}" />
</StackPanel>
0 голосов
/ 04 мая 2019

Не существует конструктора, который инициализирует Foo значениями, отличными от значений по умолчанию (null для ссылочных типов). Вот в чем причина. По крайней мере, предоставьте такой конструктор, или - более WPFic - создайте DataContext="{Binding Foo}"; это, вероятно, то, что вы хотели, однако ваш XAML неправильный: вы создаете экземпляр new все время, а не потребляете экземпляр Foo модели представления.

P.S. Более того, для UserControl s это команда выставить DependencyProperty, чтобы взять базовую модель; так это будет выглядеть как <UserControl Model="{Binding Foo}" ... />.

...