【问题标题】:WPF: How to Create ViewModel Within the ViewWPF:如何在视图中创建 ViewModel
【发布时间】:2014-12-16 20:17:51
【问题描述】:

基本上,我有一个标记问题。我想出了一些解决方案,但我不禁觉得这应该更简单。与其把你引向我复杂的道路,我想我会分享最简单的实现,并询问你将如何解决它。

MainPage.xaml

<Grid>
    <Grid.ColumnDefinitions>
        <ColumnDefinition Width="Auto" />
        <ColumnDefinition Width="6" />
        <ColumnDefinition />
        <ColumnDefinition Width="Auto" />
        <ColumnDefinition Width="6" />
        <ColumnDefinition />
        <!--Additional Columns-->
    </Grid.ColumnDefinitions>
    <!--Row Definitions-->
    <Label Grid.Row="0" Grid.Column="0" Content="Vin:" HorizontalAlignment="Right" />
    <ctrl:CommandTextBox Grid.Row="0" Grid.Column="2" Command="{Binding CreateVehicleCommand}" CommandParameter="{Binding Text, RelativeSource={RelativeSource Self}}" />
    <Label Grid.Row="0" Grid.Column="3" Content="Manufacturer:" HorizontalAlignment="Right" />
    <TextBox Grid.Row="0" Grid.Column="5" IsEnabled="False" Text="{Binding Vehicle.Manufacturer, Mode=OneWay}" />
    <!--Additional Read Only Values-->
</Grid>

鉴于上面的示例,鉴于创建车辆的命令在要创建的数据上下文(车辆)之外的约束,我如何将网格的内容放入视图中?

如果你想看看我的具体尝试,这个问题在这里UserControl's DependencyProperty is null when UserControl has a DataContext

【问题讨论】:

  • 我看不出你在哪里挣扎。为什么不能将模型注入到视图模型中?为什么会破坏文本框?如果更改 ViewModel 获取数据的方式会影响 View,那么您可能做错了。
  • @BradleyDotNET 你是对的,我所展示的作品。但这似乎不正确?我不相信我应该将我的视图模型的工厂注入到占位符视图模型中,该占位符视图模型会被工厂创建的视图模型覆盖。我开始怀疑是否可以扩展 UserControl 为我的视图模型和工厂提供第二个 DataContext ?
  • 诚实吗?你的整个设计似乎过于复杂了。为什么还需要工厂?顺便说一句,我同意你描述的过程似乎非常复杂。再说一次,你的设计也是如此。
  • @BradleyDotNET 我完全同意,并且正在寻找提供相同功能的替代设计。我已经简化了我的示例,希望得到反馈。
  • 没问题。今晚/明天早上之前我可能没有时间全面分析和发布,但我会看看它!

标签: wpf xaml mvvm


【解决方案1】:

在给定的情况下,如何将网格的内容放入视图中 限制创建车辆的命令在 要创建的DataContext(Vehicle)?

这感觉更像是一种竞争条件,而不是 MVVM 问题。我会先解决这个问题,然后再提出一个建议。

  1. ViewModel 没有理由不能包含另一个视图模型作为参考,并且该参考绑定到使用 INotifyPropertyChanged 机制。
  2. 或者您的 xaml(视图)页面包含对该页面(视图)不直接在其 DataContext 中使用的 ViewModel 的静态引用,但某个控件无法绑定到包含的数据上下文之外的该静态控制。

无论哪种方式,您都可以通过指向自身来获取数据或提供获取数据的备用管道来提供访问权限(在对您提供的另一篇帖子的回复中也提到过)。 p>


或者您可以展平您的视图模型以包含更多信息并处理此恕我直言竞争条件,这样就不会出现这种情况,并且控件和网格可以以适当的格式访问信息。

我无法完全解决这个问题,因为您更清楚现在必须解决的设计目标和危险。

【讨论】:

  • 我不确定我是否理解您对比赛条件的担忧?我知道我有许多关于创建复合 ViewModel 的选项。 MainPageViewModel 是一个组合,我同样可以创建一个包含 CreateVehicleCommand 和 VehicleViewModel 的 SubPageViewModel。我不觉得这让我走得很远。我想要做的是创建一个可以独立存在并显示 ViewModel 属性的视图。包含在此视图中,我希望能够创建 ViewModel。
  • @FrumRoll 我明白您对创建项目的视图的意思。也许问题是试图将完整的 VM 发送到控件。如果您将新创建的虚拟机的实际项目分解为单独的依赖属性会怎样?在控件中,它具有每个属性的更改通知,该通知调用一个方法,该方法查找一组完整的数据,然后“打开”控件。 (我不久前在 Silverlight 项目中做过类似的事情)无视我的想法。
【解决方案2】:

我想出了一些东西,我比较满意。这使我免于创建 100 多个复合 ViewModel,虽然它确实引入了一些不必要的复杂性,但它确实大大减少了我需要编写的复制/粘贴代码量。

VMFactoryViewModel.cs

public class CreatedViewModelEventArgs<T> : EventArgs where T : ViewModelBase
{
    public T ViewModel { get; private set; }

    public CreatedViewModelEventArgs(T viewModel)
    {
        ViewModel = viewModel;
    }
}

public class VMFactoryViewModel<T> : ViewModelBase where T : ViewModelBase
{
    private Func<string, T> _createViewModel;
    private RelayCommand<string> _createViewModelCommand;
    private readonly IDialogService _dialogService;

    /// <summary>
    /// Returns a command that creates the view model.
    /// </summary>
    public ICommand CreateViewModelCommand
    {
        get
        {
            if (_createViewModelCommand == null)
                _createViewModelCommand = new RelayCommand<string>(x => CreateViewModel(x));

            return _createViewModelCommand;
        }
    }

    public event EventHandler<CreatedViewModelEventArgs<T>> CreatedViewModel;

    private void OnCreatedViewModel(T viewModel)
    {
        var handler = CreatedViewModel;
        if (handler != null)
            handler(this, new CreatedViewModelEventArgs<T>(viewModel));
    }

    public VMFactoryViewModel(IDialogService dialogService, Func<string, T> createViewModel)
    {
        _dialogService = dialogService;
        _createViewModel = createViewModel;
    }

    private void CreateViewModel(string viewModelId)
    {
        try
        {
            OnCreatedViewModel(_createViewModel(viewModelId));
        }
        catch (Exception ex)
        {
            _dialogService.Show(ex.Message);
        }
    }
}

VMFactoryUserControl.cs

public class VMFactoryUserControl<T> : UserControl where T : ViewModelBase
{
    public static readonly DependencyProperty VMFactoryProperty = DependencyProperty.Register("VMFactory", typeof(VMFactoryViewModel<T>), typeof(VMFactoryUserControl<T>));

    public VMFactoryViewModel<T> VMFactory
    {
        get { return (VMFactoryViewModel<T>)GetValue(VMFactoryProperty); }
        set { SetValue(VMFactoryProperty, value); }
    }
}

GenericView.xaml

<ctrl:VMFactoryUserControl x:Class="GenericProject.View.GenericView"
                           x:TypeArguments="vm:GenericViewModel"
                           xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
                           xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
                           xmlns:ctrl="clr-namespace:SomeProject.Controls;assembly=SomeProject.Controls"
                           xmlns:vm="clr-namespace:GenericProject.ViewModel">
    <Grid>
        <!-- Column Definitions -->
        <!-- Row Definitions -->
        <Label Grid.Row="0" Grid.Column="0" Content="Generic Id:" HorizontalAlignment="Right" />
        <ctrl:CommandTextBox Grid.Row="0" Grid.Column="2"
                             Command="{Binding VMFactory.CreateViewModelCommand, RelativeSource={RelativeSource AncestorType={x:Type UserControl}}}"
                             CommandParameter="{Binding Text, RelativeSource={RelativeSource Self}}" />
        <Label Grid.Row="0" Grid.Column="3" Content="Generic Property:" HorizontalAlignment="Right" />
        <TextBox Grid.Row="0" Grid.Column="5" IsEnabled="False" Text="{Binding GenericProperty, Mode=OneWay}" />
        <!--Additional Read Only Values-->
    </Grid>
</ctrl:VMFactoryUserControl>

GenericView.xaml.cs

public partial class GenericView : VMFactoryUserControl<GenericViewModel>
{
    public GenericView()
    {
        InitializeComponent();
    }
}

MainPageViewModel.cs

public class MainPageViewModel : ViewModelBase
{
    private readonly IDialogService _dialogService;
    private GenericViewModel _generic;
    private readonly VMFactoryViewModel<GenericViewModel> _genericFactory;

    public GenericViewModel Generic
    {
        get { return _generic; }
        private set
        {
            if (_generic != value)
            {
                _generic = value;
                base.OnPropertyChanged("Generic");
            } 
        }
    }

    public VMFactoryViewModel<GenericViewModel> GenericFactory
    {
        get { return _genericFactory; }
    }

    private void OnGenericFactoryCreatedViewModel(object sender, CreatedViewModelEventArgs<GenericViewModel> e)
    {
        Generic = e.ViewModel;
    }

    public MainPageViewModel(IDialogService dialogService)
    {
        _dialogService = dialogService;

        _genericFactory = new VMFactoryViewModel<GenericViewModel>(_dialogService, x => new GenericViewModel(_dialogService, GetGeneric(x)));
        _genericFactory.CreatedViewModel += OnGenericFactoryCreatedViewModel;
    }

    private Generic GetGeneric(string genericId)
    {
        // Return some Generic model.
    }
}

MainPage.xaml

<Page x:Class="GenericProject.MainPage"
      xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
      xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
      xmlns:vw="clr-namespace:GenericProject.View">
    <StackPanel>
        <!-- Headers and Additional Content. -->
        <vw:EventView DataContext="{Binding Generic}"
                      VMFactory="{Binding DataContext.GenericFactory, RelativeSource={RelativeSource AncestorType={x:Type Page}}}" />
    </StackPanel>
</Page>

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-09-30
    • 1970-01-01
    • 2012-05-28
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多