Передать ViewModel в UserControl

#c# #wpf #xaml #mvvm #user-controls

#c# #wpf #xaml #mvvm #пользовательские элементы управления

Вопрос:

MySpecialView это сложный элемент управления изображением, я хотел бы повторно использовать его из разных представлений и передать его ViewModel , как в этом примере.

MainWindow.xaml

 <Window x:Class="YouBug.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:YouBug"
    mc:Ignorable="d"
    DataContext="{Binding MainViewModel}"
    Title="MainWindow" Height="350" Width="525">
<Grid>
    <local:MySpecialView ViewModel="{Binding MySpecialViewModel}"></local:MySpecialView>
</Grid>
  

MainViewModel

     public class MainViewModel
{
    public MySpecialViewModel MySpecialViewModel { get; set; }

    public MainViewModel()
    {
        MySpecialViewModel = new MySpecialViewModel();
        //gets not displayed!
        Task.Run(() => MySpecialViewModel.changeImage(5000, "C:\Users\user\Pictures\Capture.PNG"));
    }
}
  

MySpecialView.xaml

 <UserControl x:Class="YouBug.MySpecialView"
         xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
         xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
         xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
         xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
         xmlns:local="clr-namespace:YouBug"
         mc:Ignorable="d" 
         d:DesignHeight="300" d:DesignWidth="300">
<Grid>
    <Image Source="{Binding ImageSource}" />
</Grid>
  

MySpecialView.xaml.cs

     public partial class MySpecialView : UserControl
{
    public static readonly DependencyProperty ViewModelProperty = DependencyProperty.Register("ViewModel", typeof(MySpecialViewModel), typeof(MySpecialView), new FrameworkPropertyMetadata(new MySpecialViewModel(), FrameworkPropertyMetadataOptions.BindsTwoWayByDefault));

    public MySpecialViewModel ViewModel { get { return (MySpecialViewModel)GetValue(ViewModelProperty); } set { SetValue(ViewModelProperty, value); } }

    public MySpecialView()
    {
        DataContext = ViewModel;
        InitializeComponent();
    }
}
  

MySpecialViewModel

     public class MySpecialViewModel : ViewModelBase
{
    public BitmapSource imageSource { get; set; }
    public BitmapSource ImageSource { get { return imageSource; }
        set { if (value != imageSource)
            {
                imageSource = value; RaisePropertyChanged("ImageSource");
            }
        } }

    public MySpecialViewModel()
    {
        //gets displayed
        ImageSource = new BitmapImage(new Uri("C:\Users\user\Pictures\test.jpg"));

        //gets displayed aswell
        Task.Run(() => changeImage(10000, "C:\Users\user\Pictures\clickMe.png"));
    }

    public async void changeImage(int sleep, string uri)
    {
        await Task.Delay(sleep);
        BitmapSource source = new BitmapImage(new Uri(uri));
        source.Freeze();
        ImageSource = source;
    }

}
  

Но всякий раз, когда я назначаю MySpecialViewModel свойства s из MainViewModel , RaisePropertyChange событие не заставляет Image элемент или другие привязки обновляться из MySpecialViewModel .

Что я здесь делаю не так? Это вообще неправильный подход?

Комментарии:

1. DataContext = ViewModel что … что ты делаешь? Послушайте, вы должны относиться к своим UserControls как к любому другому элементу управления. Как вы думаете, текстовое поле делает что-то подобное? Нет. Ваши ViewModels / Models поступают в текстовое поле через DataContext, и вы привязываете свойства текстового поля к тому, что находится в его DataContext. Ничего подобного DataContext = ViewModel или DataContext = this ерундовой ерунды не происходит. Не делайте этого. Пожалуйста.

2. @Will Нет ничего плохого в установке ViewModel на DataContext . Проблема в том, что есть два BitmapSource свойства, когда должно быть только одно. Кроме того, экземпляр модели представления никогда не создается. @Milleu Пожалуйста, в следующий раз более тщательно изучите свои вопросы, на все вопросы в этой теме уже были даны ответы в другом месте на SO. Если вы так мало понимаете в MVVM, вам следует начать с руководства по MVVM и сначала попытаться изучить используемые концепции.

3. Основной причиной моей «бессмысленности» было получение ViewModel в коде для некоторых событий (которых нельзя избежать) — ViewModel { get {return (MySpecialViewModel )DataContext; } } было намного проще, но ответы очень помогли, спасибо! 🙂

Ответ №1:

Вы слишком привыкли к «просмотру в первую очередь» (VFA). В вашей ситуации лучше использовать «ViewModel-First-Approach» (VMFA). В VFA вы размещаете свои дочерние представления из основного представления, и каждое подпредставление связано с соответствующей ViewModel через DataContext .

В VMFA ваша ViewModel содержит ссылки на вспомогательные ViewModels. Вы предоставляете эти ссылки на ViewModel через привязку свойств, а представление отображает их через DataTemplate .

MainWindow.xaml

 <Window x:Class="YouBug.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:YouBug"
    mc:Ignorable="d"
    DataContext="{Binding MainViewModel}"
    Title="MainWindow" Height="350" Width="525">
<Grid>
    <Grid.Resources>
        <DataTemplate DataType="{x:Type vm:MySpecialViewModel}">
            <local:MySpecialViewModel />
        </DataTemplate>
    </Grid.Resources>
    <ContentControl Content={Binding MySpecialView}" />
</Grid>
  

MainViewModel

 public class MainViewModel : ViewModelBase // Not sure why you didn't subclass ViewModelBase in your question
{
    private MySpecialViewModel _mySpecialViewModel;
    public MySpecialViewModel MySpecialViewModel
    {
        get
        {
            return _mySpecialViewModel;
        }
        set
        {
            if (value != _mySpecialViewModel)
            {
                _mySpecialViewModel = value;
                RaisePropertyChanged(); // The property changed method call
            }
        }
    }

    public MainViewModel()
    {
        MySpecialViewModel = new MySpecialViewModel();
        //gets not displayed!
        Task.Run(() => MySpecialViewModel.changeImage(5000, "C:\Users\user\Pictures\Capture.PNG"));
    }
}
  

MySpecialView это не нужно DependencyProperty и не устанавливает DataContext . Часть DataContext устанавливается автоматически DataTemplate . Ваш MySpecialViewModel может остаться таким, какой он есть сейчас.

Редактировать

Я только что понял, что ваше MainWindow тоже работает DataContext неправильно.

MainWindow.xaml.cs

 public partial class MainWindow: Window
{
    public MainWindow()
    {
        InitializeComponents();
        this.DataContext = new MainViewModel();
    }
}
  

Ответ №2:

Не указывайте свойство viewmodel в своем представлении, используйте DataContext. Смотрите следующий код.

 public partial class MySpecialView : UserControl
{

    public MySpecialView()
    {
        InitializeComponent();
    }
}
  

ViewModel для специальных:

 public class MySpecialViewModel : ViewModelBase
{
    public BitmapSource imageSource { get; set; }
    public BitmapSource ImageSource { get { return imageSource; }
        set { if (value != imageSource)
            {
                imageSource = value;
 RaisePropertyChanged("ImageSource");
            }
        } }

    public MySpecialViewModel()
    {
        //gets displayed
        ImageSource = new BitmapImage(new Uri("C:\Users\user\Pictures\test.jpg"));

        //gets displayed aswell
        Task.Run(() => changeImage(10000, "C:\Users\user\Pictures\clickMe.png"));
    }

    public async void changeImage(int sleep, string uri)
    {
        await Task.Delay(sleep);
        BitmapSource source = new BitmapImage(new Uri(uri));
        source.Freeze();
        ImageSource = source;
    }

}
  

В XAML специальный:

     <UserControl x:Class="YouBug.MySpecialView"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
             xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
             xmlns:local="clr-namespace:YouBug"
             mc:Ignorable="d" 
             d:DesignHeight="300" d:DesignWidth="300">
    <Grid>
        <Image Source="{Binding ImageSource}" />
    </Grid>
  

Для основного:

 public class MainViewModel : ViewModelBase
{
    public MySpecialViewModel SpecialViewModel
    { 
        get { return _specialViewModel; }
        set
        {
          if (value != _specialViewModel)
          {
             _specialViewModel= value;
             RaisePropertyChanged("SpecialViewModel");
          }
        } 
     }

    private MySpecialViewModel _specialViewModel;

        public MainViewModel()
        {
            MySpecialViewModel = new MySpecialViewModel();
            //gets not displayed!
            Task.Run(() => MySpecialViewModel.changeImage(5000, "C:\Users\user\Pictures\Capture.PNG"));
        }
    }
  

И в XAML:

 <Window x:Class="YouBug.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:YouBug"
    mc:Ignorable="d"
    Title="MainWindow" Height="350" Width="525">
<Window.DataContext>
    <MainWindowViewModel/
</Window.DataContext>
<Grid>
    <local:MySpecialView DataContext="{Binding Path=SpecialViewModel}"></local:MySpecialView>
</Grid>