在WPF中使用MVVM模式在运行时加载XAML

3
这是一个延伸自通过运行时加载XAML的链接的问题: 我正在开发一个WPF MVVM应用程序,可以从外部源动态加载XAML内容,与上述帖子中的答案非常相似。目前为止,我已经得到了以下结果:
  1. 我的View声明了ViewModel的一个实例作为资源,并创建了该ViewModel的一个实例
  2. 在我的ViewModel构造函数中,我正在加载来自外部源(文件或db..)的XamlString属性
  3. 在我的视图中,我有一个按钮,用户在ViewModel完成加载后点击该按钮,在单击事件代码中,我正在反序列化动态加载的XAML并将其添加到我的网格中。
我的问题是,如何消除代码后台并自动化逻辑,以便在ViewModel完成获取XAML内容和初始化字符串属性后,View可以动态呈现新的xaml部分? 我应该使用某种消息总线,以便ViewModel一旦设置属性就会通知View可以添加新内容吗? 我困扰的是ViewModel确实引用了Views,但不应该负责生成UI元素。 提前感谢! 编辑: 仅澄清一下:在我的特定情况下,我并不试图将业务对象或集合(模型)绑定到UI元素(例如网格),这显然可以通过模板和绑定来完成。 我的ViewModel正在从外部源中检索整个XAML表单,并将其设置为View可用的字符串属性。

我的问题是:谁应该负责将此XAML字符串属性反序列化为UI元素,并在我的网格中以编程方式添加它?
对我来说,这更像是一个视图的责任,而不是ViewModel。 但是,据我所知的模式强制使用V-VM绑定替换任何代码后台逻辑。

这正是我面临的问题,但仍然没有清晰的解决方案:链接 我可能需要尝试@ArmedMonkey的第三个建议,通过消息订阅和回调来解决。 - Adolfo Perez
我刚发现另一个相关且有趣的问题:链接 - Adolfo Perez
2个回答

阿里云服务器只需要99元/年,新老用户同享,点击查看详情
5
我现在有一个可行的解决方案,我想分享一下。不幸的是,我没有完全摆脱代码后台,但它能够按照我的期望工作。以下是简化的工作原理: 我有一个简化的ViewModel:
public class MyViewModel : ViewModelBase
{
   //This property implements INPC and triggers notification on Set
   public string XamlViewData {get;set;}

   public ViewModel()
   {
      GetXamlFormData();  
   }

   //Gets the XAML Form from an external source (e.g. Database, File System)
   public void GetXamlFormData()
   {
       //Set the Xaml String property
       XamlViewData = //Logic to get XAML string from external source
   }
}

现在我的视图:

<UserControl.Resources>
<ViewModel:MyViewModel x:Key="Model"></ViewModel:MyViewModel>
</UserControl.Resources>
<Grid DataContext="{StaticResource Model}">
    <Grid.RowDefinitions>
        <RowDefinition Height="Auto"/>
        <RowDefinition/>
    </Grid.RowDefinitions>
    <StackPanel>
    <!-- This is the Grid used as a Place Holder to populate the dynamic content!-->
    <Grid x:Name="content" Grid.Row="1" Margin="2"/>
    <!-- Then create a Hidden TextBlock bound to my XamlString property. Right after binding happens I will trigger an event handled in the code-behind -->
    <TextBlock Name="tb_XamlString" Text="{Binding Path=XamlViewData, Mode=TwoWay, UpdateSourceTrigger=LostFocus, NotifyOnValidationError=True, ValidatesOnDataErrors=True, ValidatesOnExceptions=True}" Visibility="Hidden" Loaded="tb_XamlString_Loaded" />
    </StackPanel>
</Grid>

基本上,我在ViewModel中创建了一个绑定到XAML字符串属性的隐藏TextBlock,并将其Loaded事件连接到View的代码后面的事件处理程序:

    private void tb_XamlString_Loaded(object sender, RoutedEventArgs routedEventArgs)
    {
        //First get the ViewModel from DataContext
        MyViewModel vm = content.DataContext as MyViewModel;
        FrameworkElement rootObject = XamlReader.Parse(vm.XamlViewData) as FrameworkElement;
        //Add the XAML portion to the Grid content to render the XAML form dynamically!
        content.Children.Add(rootObject);
    }

虽然这种方法可能不是最优雅的,但可以完成任务。有些人说,在MVVM中有一些情况需要很少的代码后台。这并没有什么坏处,而且在使用VM检索和填充XamlString属性并将其公开给视图时,仍然使用了V-VM Binding原则的部分解决方案。如果我们想要单元测试XAML解析和加载功能,我们可以将其委托给一个单独的类。

我希望有人会发现这个有用!


2
我理解有困难,我的回答将基于我的理解。您应该考虑发布一个(简化的)示例以便更好地理解您在尝试做什么。 1)我认为您可能误解了MVVM的作用。 MVVM主要是一种基于绑定的模式。您的视图模型应公开包含业务对象的属性,而视图只需绑定到这些属性即可。如果我误解了您的意思,并且这正是您正在做的事情,那么您的问题在于视图需要知道何时更新属性(在反序列化XAML之后等)。有两种方法可以解决这个问题:在视图模型上使用INotifyPropertyChanged接口,或使您的视图模型继承自DependencyObject并使属性成为依赖属性。我不会在此详细说明,因为这是一个大主题,您应该在在做出决定之前在谷歌上进行研究。 2)通常情况下,如果您正在使用MVVM,则不应在视图中使用单击事件。相反,为视图模型创建类型为ICommand的属性(并创建与之匹配的ICommand实现),或使用DelegateCommand(请搜索DelegatCommand),该实现将允许您使用委托来实现接口。这样做的想法是,视图绑定到属性并直接在视图模型内执行处理程序。 3)如果您想要从视图模型向视图推送信息,则应在视图模型上创建事件,并在视图中订阅该事件,但这仅作为最后的手段,仅用于像显示新窗口等情况。通常,您应该使用绑定。 4)要更具体地说明您正在做什么,您应将Grid的ItemsSource属性绑定到视图模型上的某个属性。注意,视图模型上的属性应该是ObservableCollection<T>类型,如果要能够添加项并获得即时更新。 希望这可以帮助您。

感谢您的反馈,我已经在上面添加了更多细节。希望这样可以让它更清晰明了。 - Adolfo Perez

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,