我有一個由單選按鈕組成的選單,用于在頁面之間導航。打開應用程式時會加載第一頁。
頁面之間的導航是這樣完成的:
<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。
一切正常,但是,當我打開應用程式時,我希望第一個單選按鈕已經被選中,并且它附近的邊框是紅色的。當我導航到另一個頁面時,一切都按預期作業。
What I have tried:
Giving the first radio button a name, e.g. FirstRadioButton, and calling
FirstRadioButton.IsChecked = trueinMainView.xaml.cs. Nothing is triggered.In the
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
}
This PageName thing is part of the navigation and I am also injecting the ViewModels using dependency injection.
What is the correct approach to this?
uj5u.com熱心網友回復:
從RadioButton.IsChecked到IsActive屬性的資料系結是錯誤的。它應該會在您的 IDE 中觸發系結錯誤。系結嘗試查找MainViewModel.IsActive不存在的屬性。因此,設定
firstViewModel.IsActive = true
對視圖/資料系結沒有影響。
在ItemsControl您使用主辦StackPanel沒什么用:它包含一個專案-一個StackPanel包含所有的按鈕。一般來說,避免ItemsControl和選擇更先進的ListBox。它有一些重要的性能特性,比如 UI 虛擬化。
使用 anItemsControl或更好的 aListBox來承載RadioButton元素集合是一個好主意,但執行起來很糟糕。
您應該為導航按鈕創建資料模型,當您添加更多頁面并因此添加更多導航按鈕時,這將特別方便。IsNavigating此模型上的屬性允許控制系結到此屬性的按鈕的狀態。
該模式與您用于頁面的模式相同:View-Model-First。首先創建資料模型,讓 WPF 通過定義一個或多個DataTemplate. 在這種情況下,ListBox將為您生成視圖。
這是 WPF 的主要概念:首先考慮資料。這就是這個想法的意義DataTemplate所在。
IPage.IsActive頁面模型的屬性不應直接系結到導航按鈕。如果您確實需要此屬性,則在替換值(或您如何命名公開當前活動頁面模型的屬性)之前MainViewModl在舊頁面模型上重置此屬性,并在分配后在新頁面模型上設定此屬性它的財產。讓承載和公開頁面的模型處理和控制完整的導航邏輯。
雖然這個邏輯觸發了視圖,但它是一個純模型相關的邏輯。因此,您不應拆分此邏輯并將其部分移動到視圖(例如,通過資料系結到視圖,即使邏輯依賴于按鈕)。SelectedPageSelectedPage
你甚至可以提取這種邏輯到一個新的類如PageModelController該MainViewModel則可以使用和公開的資料系結。如果更改狀態涉及呼叫操作,
請考慮將IPage.IsActive屬性轉換為只讀屬性并添加一個IPage.Activate()和IPage.Dactivate()方法IsActive。
NavigationItem.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
由于您使用依賴注入,您應該定義一個抽象工廠來動態創建實體。如果創建 aNavigationItem需要少于三個引數,我會選擇一個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);
}
MainViewModel.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;
}
}
}
MainView.xaml
The example expects that MainViewModel is the DataContext of MainView.
<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>
You can also simplify your RadioButtonStyle.
Generally, when your triggers target elements that are part of the ControlTemplate, it's best to use the common ControlTemplate.Triggers instead of Style.Triggers for each individual element. It's also cleaner to have all triggers in one place instead of them being scattered throughout the template, only adding noise to the layout:
<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>
App.xaml.cs
Then in your application's entry point register the INavigationItemFactory factory implementation with your IoC container.
var services = new ServiceCollection();
services.AddSingleton<INavigationItemFactory, NavigationItemFactory>();
轉載請註明出處,本文鏈接:https://www.uj5u.com/gongcheng/334476.html
標籤:c# wpf mvvm radio-button
