繁体   English   中英

预选单选按钮 WPF MVVM

[英]Preselect Radio Button WPF MVVM

我有一个由单选按钮组成的菜单,用于在页面之间导航。 打开应用程序时会加载第一页。

页面之间的导航是这样完成的:

<Window.Resources>
        <DataTemplate DataType="{x:Type FirstViewModel}">
            <FirstView />
        </DataTemplate>
        <DataTemplate DataType="{x:Type SecondViewModel}">
            <SecondView />
        </DataTemplate>
</Window.Resources>

因此,每次选择新页面时都会更新 DataContext。

遵循这种方法: https : //stackoverflow.com/a/61323201/17198402

主视图.xaml

<Border Grid.Column="0">
            <Grid Background="AliceBlue">
                <Border
                    Width="10"
                    HorizontalAlignment="Left"
                    Background="SlateGray" />
                <ItemsControl>
                    <StackPanel Orientation="Vertical">
                        <RadioButton
                            Command="{Binding ShowPageCommand}"
                            CommandParameter=//not important
                            IsChecked="{Binding IsActive, UpdateSourceTrigger=PropertyChanged}"
                            Style="{StaticResource RadioButtonStyle}" 
                            Content="First"/>
                        <RadioButton
                            Command="{Binding ShowPageCommand}"
                            CommandParameter=//not important
                            IsChecked="{Binding IsActive, UpdateSourceTrigger=PropertyChanged}"
                            Style="{StaticResource RadioButtonStyle}" 
                            Content="Second"/>
                    </StackPanel>
                 </ItemsControl>
             </Grid>
</Border>

单选按钮样式

<Style x:Key="RadioButtonStyle" TargetType="RadioButton">
            <Setter Property="Template">
                <Setter.Value>
                    <ControlTemplate TargetType="{x:Type RadioButton}">
                        <Grid>
                            <Grid.ColumnDefinitions>
                                <ColumnDefinition Width="Auto" />
                                <ColumnDefinition Width="*" />
                            </Grid.ColumnDefinitions>
                            <Border Grid.Column="0" Width="10">
                                <Border.Style>
                                    <Style TargetType="Border">
                                        <Setter Property="Background" Value="Transparent" />
                                        <Style.Triggers> //MOST IMPORTANT!!
                                            <DataTrigger Binding="{Binding Path=IsChecked, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type ToggleButton}}}" Value="True">
                                                <Setter Property="Background" Value="#D50005" />
                                            </DataTrigger>
                                        </Style.Triggers>
                                    </Style>
                                </Border.Style>
                            </Border>
                            <Border
                                Grid.Column="1"
                                Background="{TemplateBinding Background}"
                                BorderBrush="{TemplateBinding BorderBrush}">
                                <ContentPresenter HorizontalAlignment="Center" VerticalAlignment="Center" />
                            </Border>
                        </Grid>
                    </ControlTemplate>
                </Setter.Value>
            </Setter>
</Style>

基本上,当单击一个单选按钮时,它绑定到的页面被加载并且按钮附近的边框变成红色——这就是它如何表示页面已打开。

IsActive是每个页面的ViewModel一个属性。

一切正常,但是,当我打开应用程序时,我希望第一个单选按钮已经被选中,并且它附近的边框是红色的。 当我导航到另一个页面时,一切都按预期工作。

我尝试过的:

  1. 为第一个单选按钮命名,例如 FirstRadioButton,并在MainView.xaml.cs调用FirstRadioButton.IsChecked = true 什么都没有触发。

  2. MainViewModel.cs

public MainViewModel(FirstViewModel firstViewModel, SecondViewModel secondViewModel)
{
    firstViewModel.IsActive = true;

    Pages = new Dictionary<PageName, IPage>
    {
         { PageName.FirstView, firstViewModel },
         { PageName.SecondView, secondViewModel }
    };

    //other code..
}

public enum PageName
{
    Undefined = 0,
    FirstView = 1,
    SecondView = 2
}

这个PageName是导航的一部分,我还使用依赖注入来注入 ViewModel。

对此的正确方法是什么?

RadioButton.IsCheckedIsActive属性的数据绑定是错误的。 它应该会在您的 IDE 中触发绑定错误。 绑定尝试查找不存在的MainViewModel.IsActive属性。 因此,设置

firstViewModel.IsActive = true

对视图/数据绑定没有影响。


用于承载StackPanelItemsControl非常无用:它包含一个项目 - 一个包含所有按钮的StackPanel 通常,避免使用ItemsControl并在其上选择更高级的ListBox 它有一些重要的性能特性,比如 UI 虚拟化。

使用ItemsControl或更好的ListBox来托管RadioButton元素的集合是一个好主意,但执行效果不佳。

您应该为导航按钮创建数据模型,当您添加更多页面并因此添加更多导航按钮时,这将特别方便。 此模型上的IsNavigating属性允许控制绑定到此属性的按钮的状态。

该模式与您用于页面的模式相同:View-Model-First。 首先创建数据模型,让 WPF 通过定义一个或多个DataTemplate动态呈现相关视图。 在这种情况下, ListBox将为您生成视图。
这是 WPF 的主要概念:首先考虑数据。 这就是DataTemplate的想法。

页面模型的IPage.IsActive属性不应直接绑定到导航按钮。 如果您确实需要此属性,则在替换SelectedPage值(或您如何命名公开当前活动页面模型的属性)并在新页面模型上设置此属性之前,在MainViewModl重置旧页面模型上的此属性将其分配给SelectedPage属性后。 让承载和公开页面的模型处理和控制完整的导航逻辑。
虽然这个逻辑触发了视图,但它是一个纯模型相关的逻辑。 因此,您不应拆分此逻辑并将其部分移动到视图(例如,通过数据绑定到视图,即使逻辑依赖于按钮)。
您甚至可以将此逻辑提取到一个新类,例如PageModelController ,然后MainViewModel可以使用该类并公开以进行数据绑定。
如果更改IsActive状态涉及调用操作,请考虑将IPage.IsActive属性转换为只读属性并添加IPage.Activate()IPage.Dactivate()方法。

导航项.cs
导航按钮数据模型。

class NavigationItem : INotifyPropertyChanged
{
  public NavigationItem(string name, PageName pageId, bool isNavigating = false)
  {
    this.Name = name;
    this.IsNavigating = isNavigating;
    this.PageId = pageId;
  }

  public string Name { get; }
  public PageName PageId { get; }

  private bool isNavigating 
  public bool IsNavigating 
  { 
    get => this.isNavigating;
    set
    {
      this.isNavigating = value;
      OnPropertyChanged();
    }
  }

  public PropertyChangedEventHandler PropertyChanged;
  protected virtual void OnPropertyChanged([CallerMemberName] string propertyname = "")
    => this.PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}

INavigationItemFactory.cs
由于您使用依赖注入,您应该定义一个抽象工厂来动态创建实例。 如果创建NavigationItem需要的参数少于三个,我会选择Func委托而不是专用的工厂类(为了可读性)。

interface INavigationItemFactory
{
  NavigationItem Create(string name, PageName pageId, bool isNavigating = false);
}

导航项工厂.cs

class NavigationItemFactory
{
  public NavigationItem Create(string name, PageName pageId, bool isNavigating = false)
    => new NavigationItem(name, pageId, isNavigating);
}

主视图模型.cs
创建单选按钮的数据模型。

class MainViewModel : INotifyPropertyChanged
{
  public ObservableCollection<NavigationItem> NavigationItems { get; }
  private INavigationItemFactory NavigationItemFactory { get; }

  public MainViewModel(INavigationItemFactory navigationItemFactory)
  {
    this.NavigationItemFactory = navigationItemFactory;
    this.NavigationItems = new ObservableCollection<NavigationItem>
    {
      this.NavigationItemFactory.Create("First Page", PageName.FirstView, true), // Preselect the related RadioButton
      this.NavigationItemFactory.Create("Second Page", PageName.SecondView),
      this.NavigationItemFactory.Create("Third Page", PageName.ThirdView)
    };
  }

  // Handle page selection and the IsActive state of the pages.
  // Consider to make the IsActive property read-only and add Activate() and Dactivate() methods, 
  // if changing this state involvs invoking operations.
  public void SelectPage(object param)
  {
    if (param is PageName pageName 
      && this.Pages.TryGetValue(pageName, out IPage selectedPage))
    {
      // Deactivate the old page
      this.SelectedPage.IsActive = false;
      this.SelectedPage = selectedPage;

      // Activate the new page
      this.SelectedPage.IsActive = true;
    }
  }
}

主视图.xaml
该示例预期MainViewModelDataContextMainView

<Window>

  <!-- Navigation bar (vertical - to change it to horizontal change the ListBox.ItemPanel) -->
  <ListBox ItemsSource="{Binding NavigationItems}">
    <ListBox.ItemTemplate>
      <DataTemplate DataType="{x:Type local:NavigationItem}">
        <RadioButton GroupName="PageNavigationButtons" 
                     Content="{Binding Name}" 
                     IsChecked="{Binding IsNavigating}" 
                     CommandParameter="{Binding PageId}"
                     Command="{Binding RelativeSource={RelativeSource AncestorType=ListBox}, Path=DataContext.ShowPageCommand}" />
      </DataTemplate>
    </ListBox.ItemTemplate>

    <!-- Remove the ListBox look&feel by overriding the ControlTemplate of ListBoxItem -->
    <ListBox.ItemContainerStyle>
      <Style TargetType="ListBoxItem">
        <Setter Property="Template">
          <Setter.Value>
            <ControlTemplate TargetType="ListBoxItem">
              <ContentPresenter />
            </ControlTemplate>
          </Setter.Value>
        </Setter>
      </Style>
    </ListBox.ItemContainerStyle>
  </ListBox>
</Window>

您还可以简化RadioButtonStyle
通常,当您触发属于ControlTemplate一部分的目标元素时,最好对每个单独的元素使用通用的ControlTemplate.Triggers而不是Style.Triggers 将所有触发器放在一个地方而不是将它们分散在整个模板中也更干净,只会给布局增加噪音:

<Style x:Key="RadioButtonStyle" TargetType="RadioButton">
  <Setter Property="Template">
    <Setter.Value>
      <ControlTemplate TargetType="{x:Type RadioButton}">
        <Grid>
          <Grid.ColumnDefinitions>
            <ColumnDefinition Width="Auto" />
            <ColumnDefinition Width="*" />
          </Grid.ColumnDefinitions>
          <Border x:Name="IsActiveIndicator" 
                  Grid.Column="0"
                  Background="Transparent" 
                  Width="10" />
          <Border Grid.Column="1"
                  Background="{TemplateBinding Background}"
                  BorderBrush="{TemplateBinding BorderBrush}"
                  BorderThickness="{TemplateBinding BorderThickness}">
            <ContentPresenter HorizontalAlignment="Center" VerticalAlignment="Center" />
          </Border>
        </Grid>

        <ControlTemplate.Triggers>
          <Trigger Property="IsChecked" Value="True">
            <Setter TargetName="IsActiveIndicator" Property="Background" Value="#D50005" />
          </Trigger>
        </ControlTemplate.Triggers>
      </ControlTemplate>
    </Setter.Value>
  </Setter>
</Style>

应用程序.xaml.cs
然后在应用程序的入口点中,将INavigationItemFactory工厂实现注册到 IoC 容器。

var services = new ServiceCollection();
services.AddSingleton<INavigationItemFactory, NavigationItemFactory>();

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM