#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>