Я плохо знаком с WPF и MVVM. Я думаю, что это - простой вопрос. Мой ViewModel выполняет асинхронный вызов для получения данных для DataGrid, который связывается с ObservableCollection в ViewModel. Когда данные загружаются, я установил надлежащее свойство ViewModel, и DataGrid отображает данные без проблемы. Однако я хочу представить визуальный индикатор для пользователя, которого загружают данные. Так, с помощью Смешения я добавил это к своей разметке:
<VisualStateManager.VisualStateGroups>
<VisualStateGroup x:Name="LoadingStateGroup">
<VisualState x:Name="HistoryLoading">
<Storyboard>
<ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="(UIElement.Visibility)" Storyboard.TargetName="HistoryGrid">
<DiscreteObjectKeyFrame KeyTime="0" Value="{x:Static Visibility.Hidden}"/>
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</VisualState>
<VisualState x:Name="HistoryLoaded">
<Storyboard>
<ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="(UIElement.Visibility)" Storyboard.TargetName="WorkingStackPanel">
<DiscreteObjectKeyFrame KeyTime="0" Value="{x:Static Visibility.Hidden}"/>
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</VisualState>
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
Я думаю, что знаю, как изменить состояние в моем коде - позади (что-то подобное этому):
VisualStateManager.GoToElementState(LayoutRoot, "HistoryLoaded", true);
Однако место, где я хочу сделать, это находится в методе завершения ввода-вывода моего ViewModel, который не имеет ссылки на, он - соответствующее Представление. Как я выполнил бы это использование шаблона MVVM?
Вы можете сделать что-то вроде этого:
XAML
<Window x:Class="WpfSOTest.BusyWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:WpfSOTest"
Title="BusyWindow"
Height="300"
Width="300">
<Window.Resources>
<local:VisibilityConverter x:Key="VisibilityConverter" />
</Window.Resources>
<Grid>
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Border Grid.Row="0">
<Grid>
<Border>
<Rectangle Width="400"
Height="400"
Fill="#EEE" />
</Border>
<Border Visibility="{Binding IsBusy, Converter={StaticResource VisibilityConverter}}">
<Grid>
<Rectangle Width="400"
Height="400"
Fill="#AAA" />
<TextBlock Text="Busy"
HorizontalAlignment="Center"
VerticalAlignment="Center" />
</Grid>
</Border>
</Grid>
</Border>
<Border Grid.Row="1">
<Button Click="ChangeVisualState">Change Visual State</Button>
</Border>
</Grid>
Код:
public partial class BusyWindow : Window
{
ViewModel viewModel = new ViewModel();
public BusyWindow()
{
InitializeComponent();
DataContext = viewModel;
}
private void ChangeVisualState(object sender, RoutedEventArgs e)
{
viewModel.IsBusy = !viewModel.IsBusy;
}
}
public class ViewModel : INotifyPropertyChanged
{
protected Boolean _isBusy;
public Boolean IsBusy
{
get { return _isBusy; }
set { _isBusy = value; RaisePropertyChanged("IsBusy"); }
}
public ViewModel()
{
IsBusy = false;
}
public event PropertyChangedEventHandler PropertyChanged;
public void RaisePropertyChanged(String propertyName)
{
PropertyChangedEventHandler temp = PropertyChanged;
if (temp != null)
{
temp(this, new PropertyChangedEventArgs(propertyName));
}
}
}
class VisibilityConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
switch (((Boolean)value))
{
case true:
return Visibility.Visible;
}
return Visibility.Collapsed;
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
}
----------------------- Обновленный код --- --------------------
XAML
<Grid>
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Border Grid.Row="0">
<Grid>
<local:MyBorder IsBusy="{Binding IsBusy}">
<Grid>
<TextBlock Text="{Binding IsBusy}"
HorizontalAlignment="Center"
VerticalAlignment="Center" />
</Grid>
</local:MyBorder>
</Grid>
</Border>
<Border Grid.Row="1">
<Button Click="ChangeVisualState">Change Visual State</Button>
</Border>
</Grid>
Шаблон
<Style TargetType="{x:Type local:MyBorder}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type local:MyBorder}">
<Border Name="RootBorder">
<Border.Background>
<SolidColorBrush x:Name="NormalBrush"
Color="Transparent" />
</Border.Background>
<VisualStateManager.VisualStateGroups>
<VisualStateGroup Name="CommonGroups">
<VisualState Name="Normal" />
</VisualStateGroup>
<VisualStateGroup Name="CustomGroups">
<VisualState Name="Busy">
<VisualState.Storyboard>
<Storyboard>
<ColorAnimation Storyboard.TargetName="NormalBrush"
Storyboard.TargetProperty="Color"
Duration="0:0:0.5"
AutoReverse="True"
RepeatBehavior="Forever"
To="#EEE" />
</Storyboard>
</VisualState.Storyboard>
</VisualState>
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
<ContentPresenter />
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
Пользовательский элемент
[TemplateVisualState(GroupName = "CustomGroups", Name = "Busy")]
public class MyBorder : ContentControl
{
static MyBorder()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(MyBorder), new FrameworkPropertyMetadata(typeof(MyBorder)));
}
public Boolean IsBusy
{
get { return (Boolean)GetValue(IsBusyProperty); }
set { SetValue(IsBusyProperty, value); }
}
public static readonly DependencyProperty IsBusyProperty =
DependencyProperty.Register("IsBusy", typeof(Boolean), typeof(MyBorder), new UIPropertyMetadata(IsBusyPropertyChangedCallback));
static void IsBusyPropertyChangedCallback(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
(d as MyBorder).OnIsBusyPropertyChanged(d, e);
}
private void OnIsBusyPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
if (Convert.ToBoolean(e.NewValue))
{
VisualStateManager.GoToState(this, "Busy", true);
}
else
{
VisualStateManager.GoToState(this, "Normal", true);
}
}
}
Стандартный способ сделать это - обычно иметь свойство во вьюмодели (либо зависимое свойство, либо участвующее в INotifyPropertyChanged), которое будет сигнализировать о том, что данные загружаются - возможно bool IsLoadingData
или подобное. Вы устанавливаете его в true, когда начинаете загрузку, и устанавливаете его в false, когда заканчиваете.
Затем вы привязываете триггер или визуальное состояние к этому свойству в представлении и используете представление для описания того, как представить пользователю, что данные загружаются.
Этот подход сохраняет разделение, когда модель представления является логическим представлением пользовательского представления, и ей не нужно участвовать в фактическом отображении - или иметь знания об анимации, визуальных состояниях и т.д.
Чтобы использовать DataStateBehavior для изменения визуальных состояний на основе привязки в Silverlight:
<TheThingYouWantToModify ...>
<i:Interaction.Behaviors>
<ei:DataStateBehavior Binding="{Binding IsLoadingData}" Value="true" TrueState="HistoryLoading" FalseState="HistoryLoaded" />
</i:Interaction.Behaviors>
</TheThingYouWantToModify >
В прошлом я делал так: объявлял событие в своей виртуальной машине, на которое подписывается View. Затем, когда я хочу указать, что индикатор занятости должен исчезнуть, я поднимаю событие внутри VM.