【问题标题】:How can I change the VisualState in a View from the ViewModel?如何从 ViewModel 更改视图中的 VisualState?
【发布时间】:2011-03-04 02:13:42
【问题描述】:

我是 WPF 和 MVVM 的新手。我认为这是一个简单的问题。我的 ViewModel 正在执行异步调用以获取绑定到 ViewModel 中的 ObservableCollection 的 DataGrid 的数据。加载数据后,我设置了正确的 ViewModel 属性,DataGrid 可以毫无问题地显示数据。但是,我想为用户介绍数据正在加载的视觉提示。因此,使用 Blend,我将其添加到我的标记中:

        <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 的 I/O 完成方法中,它没有对其对应视图的引用。我将如何使用 MVVM 模式来实现这一点?

【问题讨论】:

    标签: wpf silverlight mvvm


    【解决方案1】:

    执行此操作的标准方法通常是在您的视图模型中拥有一个属性(依赖属性或参与 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 >
    

    【讨论】:

    • 我喜欢这种方法。抱歉这么无知,但是如何将视觉状态绑定到属性?
    • 我相信 silverlight 和 wpf 4 之间是不同的。但是,在 SL4 中,您可以使用 DataStateBehavior 或 DataStateSwitchBehavior - 没有很多示例,但足以开始使用。您命名您的视觉状态,并将interaction.behavior 节点添加到具有datastatebehavior 的目标控件,以更改为在绑定属性上触发的命名视觉状态。
    【解决方案2】:

    你可以这样做:

    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);
            }
        }
    }
    

    【讨论】:

    • 这是一个有趣的技术。谢谢你。我可能会走这条路——但我首先想看看是否可以根据对 ViewModel 属性的更改以某种方式更改视图的 VisualState。我认为这是我做动画的唯一方法——比如基于状态的淡入淡出。
    • 我在这里使用了可见性属性,但是您可以做的是在您的自定义控件中,创建布尔类型的依赖属性,将 IsBusy 属性绑定到该属性并更改控件中的可视状态当属性更改为特定值时自身。
    【解决方案3】:

    我过去所做的是在我的 VM 中声明一个 View 订阅的事件。然后,当我想指示忙碌指示符应该消失时,我在 VM 内引发事件。

    【讨论】:

    • 属性胜过事件。
    猜你喜欢
    • 1970-01-01
    • 2011-11-03
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-06-21
    • 1970-01-01
    相关资源
    最近更新 更多