Wpf 如何在视图中创建控件的新实例而不违反MVVM

Wpf 如何在视图中创建控件的新实例而不违反MVVM,wpf,silverlight,mvvm,Wpf,Silverlight,Mvvm,我遇到了与所描述的类似的问题。建议的解决方案是为我们希望呈现的每个now页面(PDF)创建一个新的WebBrowser控件(覆盖旧的WebBrowser控件)。 在MVVM中创建新控件的正确方法是什么?我试图让虚拟机不知道视图的实现。为什么虚拟机需要知道?为什么视图不能挂接到一个适当的事件(如果您愿意,可以定义一个,或者只使用属性changed)并重新创建控件 使用名为CreateBrowser()的方法在ViewModel中创建名为IBrowserCreator的接口 在ViewModel中创

我遇到了与所描述的类似的问题。建议的解决方案是为我们希望呈现的每个now页面(PDF)创建一个新的WebBrowser控件(覆盖旧的WebBrowser控件)。
在MVVM中创建新控件的正确方法是什么?我试图让虚拟机不知道视图的实现。

为什么虚拟机需要知道?为什么视图不能挂接到一个适当的事件(如果您愿意,可以定义一个,或者只使用
属性changed
)并重新创建控件

  • 使用名为CreateBrowser()的方法在ViewModel中创建名为IBrowserCreator的接口
  • 在ViewModel中创建名为ViewHelper的静态类,并向其中添加名为BrowserCreator的IBrowserCreator类型的静态属性
  • 在视图层中,创建一个名为BrowserCreator的新类,该类实现ViewModel.IBrowserCreator
  • 在视图初始化代码中,实例化BrowserCreator,并将其分配给ViewModel.ViewHelper.BrowserCreator
  • 从ViewModel中,您现在应该能够调用:

    ViewHelper.BrowserCreator.CreateBrowser()


    显然,这个答案只是一个框架,但它应该给你一个大概的想法。您需要实现CreateBrowser方法来满足您的具体需求。

    为什么不简单地使用Datatemplate,让WPF来完成其余的工作

  • 使用webbrowser创建用户控件。您必须添加附加属性,因为无法直接绑定到源

    <UserControl x:Class="WpfBrowser.BrowserControl"
             xmlns:WpfBrowser="clr-namespace:WpfBrowser" >
      <Grid>
        <WebBrowser WpfBrowser:WebBrowserUtility.BindableSource="{Binding MyPdf}"/>
      </Grid>
    </UserControl>
    
  • 获取pageviewmodel,添加pdfviewmodel,并在视图中获取contentcontrol

    public class MyPageViewmodel: INotifyPropertyChanged
    {
      private MyPdfVM _myPdfStuff;
      public MyPdfVM MyPdfStuff
      {
        get { return _myPdfStuff; }
        set { _myPdfStuff = value; this.NotifyPropertyChanged(()=>this.MyPdfStuff);}
      }
    
      public MyViewmodel()
      {
        this.MyPdfStuff = new MyPdfVM();
      }
    
      public event PropertyChangedEventHandler PropertyChanged;
      protected void NotifyPropertyChanged<T>(Expression<Func<T>> property)
      {
        var propertyInfo = ((MemberExpression)property.Body).Member as PropertyInfo;
    
        if (propertyInfo == null)
        {
            throw new ArgumentException("The lambda expression 'property' should point to a valid Property");
        }
    
        var handler = PropertyChanged;
    
        if (handler != null)
            handler(this, new PropertyChangedEventArgs(propertyInfo.Name));
      }
    }
    
    当您更改MyPdfStuff属性时,webbroswer将更新pdf

    附属财产

    public static class WebBrowserUtility
    {
        public static readonly DependencyProperty BindableSourceProperty =
            DependencyProperty.RegisterAttached("BindableSource", typeof(string), typeof(WebBrowserUtility), new UIPropertyMetadata(null, BindableSourcePropertyChanged));
    
        public static string GetBindableSource(DependencyObject obj)
        {
            return (string)obj.GetValue(BindableSourceProperty);
        }
    
        public static void SetBindableSource(DependencyObject obj, string value)
        {
            obj.SetValue(BindableSourceProperty, value);
        }
    
        public static void BindableSourcePropertyChanged(DependencyObject o, DependencyPropertyChangedEventArgs e)
        {
            WebBrowser browser = o as WebBrowser;
            if (browser != null)
            {
                string uri = e.NewValue as string;
                browser.Source = string.IsNullOrWhiteSpace(uri) ? null:new Uri(uri);
            }
        }
    
    }
    

    编辑:添加一些代码,以便您可以看到,如果更改PDFViewmodel,您的浏览器控件将显示新的pdf。

    这不起作用。浏览器在更改URL时引发异常。请看我提到的另一个问题。它确实像一个咒语。我将添加一些代码,以便您可以尝试自己。谢谢!我真的很喜欢这个解决方案,但是我错过了Kent Boogaart建议的更简单的方法,这在我的例子中是可能的。我真的不喜欢这个解决方案中有一个静态属性。如果同时存在多个VM和V实例,该怎么办?在某些情况下,该解决方案可能是合适的,但我不建议这样做。Kent Boogaart的方法既简单又灵活。是的,这就是MVVM真正的意义所在。我想补充一点,证明这种方法的正确性。如果在某个时刻OP决定他/她需要在面板、窗口或其他任何地方显示内容,而不是在
    WebBrowser
    中,那么唯一需要更改的是视图中决定如何显示该内容的一小段代码。虚拟机仍然完好无损。
        <Window x:Class="WpfBrowser.MainWindow"
                xmlns:WpfBrowser="clr-namespace:WpfBrowser" 
                Title="MainWindow" Height="350" Width="525">
         <Window.Resources>
           <DataTemplate DataType="{x:Type WpfBrowser:MyPdfVM}">
            <WpfBrowser:BrowserControl />
           </DataTemplate>
         </Window.Resources>
         <Grid>
           <Grid.RowDefinitions>
            <RowDefinition Height="64*" />
            <RowDefinition Height="247*" />
           </Grid.RowDefinitions>
          <Button Content="Button" Height="23" HorizontalAlignment="Left" Margin="32,14,0,0" Name="button1" VerticalAlignment="Top" Width="75" Click="button1_Click" />
          <ContentControl Grid.Row="1" Content="{Binding MyPdfStuff}"/>
         </Grid>
        </Window>
    
    public partial class MainWindow : Window
    {
        private MyViewmodel _data;
        public MainWindow()
        {
            _data = new MyViewmodel();
            InitializeComponent();
            this.DataContext = _data;
        }
    
        private void button1_Click(object sender, RoutedEventArgs e)
        {
            this._data.MyPdfStuff = new MyPdfVM() { MyPdf = new Uri(@"your other pdf path for testing") };
        }
    }
    
    public static class WebBrowserUtility
    {
        public static readonly DependencyProperty BindableSourceProperty =
            DependencyProperty.RegisterAttached("BindableSource", typeof(string), typeof(WebBrowserUtility), new UIPropertyMetadata(null, BindableSourcePropertyChanged));
    
        public static string GetBindableSource(DependencyObject obj)
        {
            return (string)obj.GetValue(BindableSourceProperty);
        }
    
        public static void SetBindableSource(DependencyObject obj, string value)
        {
            obj.SetValue(BindableSourceProperty, value);
        }
    
        public static void BindableSourcePropertyChanged(DependencyObject o, DependencyPropertyChangedEventArgs e)
        {
            WebBrowser browser = o as WebBrowser;
            if (browser != null)
            {
                string uri = e.NewValue as string;
                browser.Source = string.IsNullOrWhiteSpace(uri) ? null:new Uri(uri);
            }
        }
    
    }