【问题标题】:WPF - Best way of responding to changes in a ViewModel at Page/Window levelWPF - 在页面/窗口级别响应 ViewModel 更改的最佳方式
【发布时间】:2009-03-20 16:01:09
【问题描述】:

我正在开发 XBAP,我有一个简单的要求。

整个主页的DataContext 设置为我的UserViewModel 的一个实例。 UserViewModel 有一个名为 AuthenticationStateDependencyProperty,它是一个枚举值,例如 'Authenticated'、'NotAutheticated' 和 'AuthenticationFailed'。

现在,我需要通过隐藏/显示页面上的各种元素来响应此值的任何变化。

最好的方法是什么(以及在哪里)?

【问题讨论】:

    标签: wpf mvvm triggers viewmodel


    【解决方案1】:

    正如您所提到的,您不能直接在控件上使用 DataTrigger。一种解决方法是在每个需要隐藏的控件上使用一种样式。

    <Grid>
        <Rectangle Fill="Red" />
        <Grid.Style>
            <Style TargetType="Grid">
                <Style.Triggers>
                    <DataTrigger Binding="{Binding Test}" Value="true">
                        <Setter Property="Visibility" Value="Collapsed" />
                    </DataTrigger>
                </Style.Triggers>
            </Style>
        </Grid.Style>
    </Grid>
    

    【讨论】:

    • 您也可以像在其他答案中一样在窗口上设置样式并使用 Setter.TargetName。这样一来,您将拥有 1 个触发器和许多 setter。
    • 如果这行得通,我会感到非常惊讶,因为我认为 TargetName 不会因为使用触发器的上下文而解析。
    【解决方案2】:

    更好的方法是使用一个名为“AuthenticationStateToVisibilityConverter”的转换器,用于将控件的 Visibility 属性绑定到数据上下文的 AuthenticationState 属性。

    【讨论】:

    • 哈哈。早该发现!你不能说现在是星期五 5:00。
    【解决方案3】:

    最好的方法是使用 DataTrigger。所以是这样的:

    <Window.Triggers>
        <DataTrigger Binding="{Binding AuthenticationState}" Value="NotAuthenticated">
            <Setter TargetName="nameOfControl" Property="Visibility" Value="Collapsed" />
        </DataTrigger>
        ...
        <TextBox x:Name="nameOfControl" />
    </Window.Triggers>
    

    只要您的 UserViewModel 对象在 Window 的 DataContext 中,那么这应该可以工作!

    【讨论】:

    • 我做不到。 Window 是一个 FrameworkElement,它只接受其触发器集合中的 EventTriggers。
    • 这是一个好点,我忘记了。我很少遇到这个问题,因为我所做的几乎所有事情都是在 Control/DataTemplates 中完成的。
    【解决方案4】:

    设法使用样式对其进行排序。这很痛苦,但它有效!

    完整来源如下。

    <Grid x:Name="contentGrid" Grid.Row="1">
            <!--login-->
            <controls:LoginControl>
                <controls:LoginControl.Style>
                    <Style>
                        <Setter Property="Control.Opacity" Value="0"/>
                        <Setter Property="Control.IsHitTestVisible" Value="False"/>
                        <Style.Triggers>
                            <DataTrigger Binding="{Binding Source={StaticResource UserViewModel},Path=UserAuthenticationState}"
                                         Value="{x:Static model:AuthenticationState.NotAuthenticated}">
                                <Setter Property="Control.IsHitTestVisible" Value="True"/>
                                <DataTrigger.EnterActions>
                                    <BeginStoryboard>
                                        <Storyboard>
                                            <DoubleAnimation To="1" Duration="0:0:2"
                                                             Storyboard.TargetProperty="Opacity"></DoubleAnimation>
                                        </Storyboard>
                                    </BeginStoryboard>
                                </DataTrigger.EnterActions>
                                <DataTrigger.ExitActions>
                                    <BeginStoryboard>
                                        <Storyboard>
                                            <DoubleAnimation To="0" Duration="0:0:2"
                                                             Storyboard.TargetProperty="Opacity"></DoubleAnimation>
                                        </Storyboard>
                                    </BeginStoryboard>
                                </DataTrigger.ExitActions>
                            </DataTrigger>
                        </Style.Triggers>
                    </Style>
                </controls:LoginControl.Style>
            </controls:LoginControl>    
    
            <!--slider-->
            <slider:PageSlider>
                <Button>1</Button>
                <Button>2</Button>
                <Button>3</Button>
                <slider:PageSlider.Style>
                    <Style>
                        <Setter Property="Control.Opacity" Value="0"/>
                        <Setter Property="Control.IsHitTestVisible" Value="False"/>
                        <Style.Triggers>
                            <DataTrigger Binding="{Binding Source={StaticResource UserViewModel},Path=UserAuthenticationState}"
                                         Value="{x:Static model:AuthenticationState.Authenticated}">
                                <Setter Property="Control.IsHitTestVisible" Value="True"/>
                                <DataTrigger.EnterActions>
                                    <BeginStoryboard>
                                        <Storyboard>
                                            <DoubleAnimation To="1" Duration="0:0:2"
                                                             Storyboard.TargetProperty="Opacity"></DoubleAnimation>
                                        </Storyboard>
                                    </BeginStoryboard>
                                </DataTrigger.EnterActions>
                                <DataTrigger.ExitActions>
                                    <BeginStoryboard>
                                        <Storyboard>
                                            <DoubleAnimation To="0" Duration="0:0:2"
                                                             Storyboard.TargetProperty="Opacity"></DoubleAnimation>
                                        </Storyboard>
                                    </BeginStoryboard>
                                </DataTrigger.ExitActions>
                            </DataTrigger>
                        </Style.Triggers>
                    </Style>
                </slider:PageSlider.Style>
            </slider:PageSlider>
        </Grid>
    

    【讨论】:

    • 您需要一个在不透明度为 0 时折叠控件的触发器。
    【解决方案5】:

    实际上,最好的方法是从视图模型中公开适当的属性。这使您的逻辑更加集中并且更容易测试。此外,它的性能优于转换器。毕竟,它是一个视图模型。因此,它应该对视图进行建模。如果视图需要一个属性来告诉它何时隐藏/显示面板,请将这样的属性添加到您的视图模型中。

    【讨论】:

    • 我确实尝试过这样做,但该属性的钩子在哪里?我可以使用谁的触发器集合来执行切换?
    • 对视图模型使用 DependencyObjects(和 DependencyPropertys)是一种糟糕的做法,恕我直言。最好使用实现 INotifyPropertyChanged 的​​ POCO。也就是说,您可以使用 DependencyPropertyDescriptor.AddValueChanged() 来监听更改。
    • 您将很快拥有像“ShowViewModel”这样的属性爆炸式增长。然后,您将遇到无法在多个位置拥有相同 ViewModel 实例的情况,因为不同场景的预期行为不同。转换器是可维护的,测试也同样简单。
    • 还有其他方法可以分解出逻辑。基础视图模型类,例如。实用程序类是另一个(基本上是转换器)。这可以让您的 XAML 更简洁、更高效。
    • 顺便说一句,不将 DependencyObjects 用作 VM 的原因还有很多。我计划很快就该主题发表一篇博文,并将从此处链接到该主题。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2021-12-10
    • 2010-12-09
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-07-15
    • 1970-01-01
    相关资源
    最近更新 更多