WPF Style
https://github.com/MahApps/MahApps.Metro
https://github.com/HandyOrg/HandyControl
Style定义:
如果Style没有定义Key,则会自动应用到所有的相关组件上,如果不想应用默认样式的话,应该在对应的组件上使用Style=”{x:Null}”,移除自动应用的Style
<Style x:Key="BigFontButtonStyle" TargetType="Button">
<Setter Property="FontSize" Value="18"/>
<EventSetter Event="TextBlock.MouseLeave" Handler="element_MouseLeaveHandler"/>
</Style
绑定到附加属性
<Setter Property="Height">
<Setter.Value>
<Binding Path="(attachHelper:DataGridHelper.DataGridCellHeight)"
RelativeSource="{RelativeSource Mode=FindAncestor,AncestorType=DataGrid}"/>
</Setter.Value>
</Setter>
Brush属性设置:
Brush存在于两个命名空间,System.Windows.Media和System.Drawing, 控件的属性是System.Windows.Media中的。
Style中依赖属性赋值的优先级:
https://learn.microsoft.com/en-us/dotnet/desktop/wpf/properties/dependency-property-value-precedence
本地属性:在控件中直接设置的属性或者通过Binding设置。
本地属性>TemplatedParent>Style trigger中的属性设置 >Template trigger中的属性设置>Style中Setter的属性设置
因此,当本地属性设置后,如果想在Style的trigger中修改,则不会生效
解决办法:将本地属性移到Style 的setter中,这样Style的trigger会优先生效。
demo:
<StackPanel>
<StackPanel.Resources>
<ControlTemplate x:Key="ButtonTemplate" TargetType="{x:Type Button}">
<Border Background="{TemplateBinding Background}" BorderThickness="{TemplateBinding BorderThickness}"
BorderBrush="{TemplateBinding BorderBrush}">
<ContentPresenter HorizontalAlignment="Center" VerticalAlignment="Center" />
</Border>
</ControlTemplate>
</StackPanel.Resources>
<Button Template="{StaticResource ButtonTemplate}" Background="Red">
<Button.Style>
<Style TargetType="{x:Type Button}">
<Setter Property="Background" Value="Blue"/>
<Style.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter Property="Background" Value="Yellow" />
</Trigger>
</Style.Triggers>
</Style>
</Button.Style>
Which color do you expect?
</Button>
</StackPanel>
在上面的例子中,Button的本地属性Background设置优先级最高,所以会显示Red,Style和Template中的不会生效。
当移除Button的本地属性Background后,Style的trigger的属性设置优先级会变高。
Styles继承
Styles是支持继承的,通过设置BasedOn属性来实现。
<Style BasedOn="{StaticResource .....}" TargetType=""/>
Style继承后,关于Style.Trigger执行的问题:
当父Style和子Style都添加了同一个Trigger,则当触发时,
会先执行父Style的Trigger,再执行子Style的Trigger,如果子Style的Trigger中属性设置没有完全覆盖父Style中的Trigger属性设置,则会先后同时起作用。
<Style x:Key="Styles.DataGridRow.Base" TargetType="DataGridRow">
<Setter Property="Background" Value="White"/>
<Setter Property="BorderBrush" Value="#e1e6eb"/>
<Setter Property="BorderThickness" Value="0"/>
<Style.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter Property="BorderBrush" Value="#2FA6FF" />
</Trigger>
<Trigger Property="IsSelected" Value="True">
<Setter Property="BorderBrush" Value="Orange" />
<Setter Property="BorderThickness" Value="4"/>
<Setter Property="Background" Value="Red" />
</Trigger>
</Style.Triggers>
</Style>
<Style x:Key="Styles.DataGridRow.Default" TargetType="DataGridRow"
BasedOn="{StaticResource Styles.DataGridRow.Base}">
<Setter Property="Background" Value="White"/>
<Setter Property="BorderBrush" Value="#238865"/>
<Setter Property="BorderThickness" Value="1"/>
<Style.Triggers>
<Trigger Property="IsSelected" Value="True">
<!--<Setter Property="BorderBrush" Value="#668db3" />-->
<Setter Property="BorderThickness" Value="10"/>
</Trigger>
</Style.Triggers>
</Style>
如上图所示,IsSelected发生时,会先执行Styles.DataGridRow.Base中的Trigger设置,再去执行Styles.DataGridRow.Default中的Triger设置。
在Style中设置EventTrigger
需要在代码中实现Handler。
<ListBox Grid.Row="0" Grid.Column="0" ItemsSource="{Binding SchoolList}" Margin="10,10">
<ListBox.ItemContainerStyle>
<Style TargetType="{x:Type ListBoxItem}">
<EventSetter Event="PreviewMouseMove" Handler="ListBoxItem_PreviewMouseMove" />
</Style>
</ListBox.ItemContainerStyle>
<ListBox.ItemTemplate>
<DataTemplate >
<Grid helper:GridHelper.Cols="* *">
<TextBlock Grid.Column="0" Text="{Binding Name}" Margin="10,0"/>
<TextBlock Grid.Column="1" Text="{Binding Location}"/>
</Grid>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
Triggers
Trigger是按照定义的顺序执行的,如果两个Trigger都满足条件,则会依次生效,后面的Trigger会把前面的Trigger中设置的相同的属性值覆盖。
可以定义在:
Style.Trigger, DataTemplate.Triggers, ControlTemplate.Triggers, FrameworkElement.Triggers
注意:FrameworkElement.Triggers中的Trigger只能是EventTrigger。
还可以设置当Trigger进入和退出时,做一些操作,如添加入场或出场动画,
<Trigger>
<Trigger.EnterActions>
</Trigger.EnterActions>
<Trigger.ExitActions>
</Trigger.ExitActions>
</Trigger>
Basic Property Trigger
当控件的属性改变时,会Trigger对应的操作。
<Style TargetType="Button">
<Style.Triggers>
<Trigger Property="Control.IsFocused" Value="True">
<Setter Property="Control.ForeGround" Value="DardRed"/>
</Trigger>
<Trigger Property="Grid.Row" Value="1">
<Setter Property="Control.ForeGround" Value="DardRed"/>
</Trigger>
</Style.Triggers>
</Style>
<Style x:Key="NavigationBarButton" TargetType="Button">
<Setter Property="Background" Value="Transparent"/>
<Setter Property="BorderThickness" Value="0"/>
<Setter Property="FocusVisualStyle" Value="{x:Null}"/>
<Setter Property="Height" Value="146"/>
<Setter Property="HorizontalContentAlignment" Value="Center"/>
<Setter Property="Margin" Value="20,0"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="Button">
<Border BorderBrush="{TemplateBinding BorderBrush}"
Background="{TemplateBinding Background}"
BorderThickness="{TemplateBinding BorderThickness}">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<Image Stretch="Uniform"
Source="{Binding (attach:ButtonHelper.NormalImageSource),RelativeSource={RelativeSource Mode=TemplatedParent}}" Width="64"/>
<ContentPresenter Grid.Row="1"
HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
VerticalAlignment="{TemplateBinding VerticalContentAlignment}"/>
</Grid>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
<Style.Triggers>
<Trigger Property="IsPressed" Value="True">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="Button">
<Border BorderBrush="{TemplateBinding BorderBrush}"
Background="{TemplateBinding Background}"
BorderThickness="{TemplateBinding BorderThickness}">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<Image Stretch="Uniform"
Source="{Binding (attach:ButtonHelper.PressedImageSource),RelativeSource={RelativeSource Mode=TemplatedParent}}" Width="64"/>
<ContentPresenter Grid.Row="1"
HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}" VerticalAlignment="{TemplateBinding VerticalContentAlignment}"/>
</Grid>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Trigger>
</Style.Triggers>
</Style>
EventTrigger
当事件发生时,会Trigger相应的操作。
<Style x:Key="BigFontStyle">
<Style.Trigers>
<EventTrigger RoutedEvent="Mouse.MouseEnter">
<EventTrigger.Actions>
<BeginStoryborad>
<DoubleAnimation
Duration="0:0:0.2"
Storyboard.TargetProperty="FontSize"
To="22" />
</BeginStoryboard>
</EventTrigger.Actions>
</EventTrigger>
</Style.Triggers>
</Style>
MultiTrigger
注意MultiTrigger和MultiDataTrigger的使用区别:MultiTrigger中只能使用Property和Value来设置条件
MultiTrigge
当多个条件同时满足要求后,才会Trigger对应的操作。
<Style TargetType="{x:Type DataGridCell}">
<Style.Triggers>
<MultiTrigger>
<MultiTrigger.Conditions>
<Condition Property="IsSelected" Value="True"/>
<Condition Property="IsFocused" Value="False"/>
</MultiTrigger.Conditions>
//一个Condition多种Setter
<MultiTrigger.Setters>
<Setter Property="Background" Value="Transparent"/>
<Setter Property="Opacity" Value="0.9"/>
</MultiTrigger.Setters>
<MultiTrigger.EnterActions>
<BeginStoryboard Name="bb">
<Storyboard>
<ColorAnimation
Storyboard.TargetName="ellipseFlashUI"
Duration="0:0:0.5"
Storyboard.TargetProperty="(Fill).(SolidColorBrush.Color)"
From="{StaticResource flashColor}" To="White"
AutoReverse="True" RepeatBehavior="Forever"/>
</Storyboard>
</BeginStoryboard>
</MultiTrigger.EnterActions>
<MultiTrigger.ExitActions>
<StopStoryboard BeginStoryboardName="bb" />
</MultiTrigger.ExitActions>
</MultiTrigger>
</Style.Triggers>
</Style>
DataTrigger & MultiDataTrigger
DataTrigger
可以根据不同的Data Value,来设置控件不同的Template
普通的Trigger中使用的是控件本身的属性,而DataTrigger的使用范围要更大,通过使用Binding,可以Binding到自身或者其他Control的属性上(需要设置ElementName或者RelativeSource),或者ViewModel上的属性,或者使用附加属性
在DataTrigger中使用绑定,默认是绑定到DataContext上的属性,如果不是绑定到ViewModel中的属性,则无论是绑定到自身控件设置的属性和附加属性或者其他,都需要设置RelativeSource才会起作用
<!--绑定到控件自身的属性上 -->
<ToggleButton x:Name="btn" Content="Go" >
<ToggleButton.Style>
<Style TargetType="ToggleButton">
<Style.Triggers>
<DataTrigger Binding="{Binding Path=IsChecked,
RelativeSource={RelativeSource Self}}" Value="True">
<Setter Property="Foreground" Value="Blue"/>
</DataTrigger>
</Style.Triggers>
</Style>
</ToggleButton.Style>
</ToggleButton>
<!--绑定到控件自身的属性上 -->
<ToggleButton x:Name="btn" Content="Go" >
<ToggleButton.Style>
<Style TargetType="ToggleButton">
<Style.Triggers>
<DataTrigger Binding="{Binding Path=IsChecked,ElementName=btn}"
Value="True">
<Setter Property="Foreground" Value="Blue"/>
</DataTrigger>
<DataTrigger Binding="{Binding Path=IsChecked,ElementName=btn}"
Value="False">
<Setter Property="Foreground" Value="Red"/>
</DataTrigger>
</Style.Triggers>
</Style>
</ToggleButton.Style>
</ToggleButton>
DataTrigger在Style.Triggers中和ControlTemplate.Triggers使用有没有什么区别?
在DataTrigger中绑定到ViewModel:
[ObservableObject]
public partial class MainWindowViewModel
{
[ObservableProperty]
private bool _Start;
[RelayCommand]
private void Change()
{
Start = !_Start;
}
}
<Style x:Key="rec" TargetType="Rectangle">
<Setter Property="Stroke" Value="Black"/>
<Style.Triggers>
<DataTrigger Binding="{Binding Start}" Value="False">
<DataTrigger.Setters>
<Setter Property="Fill" Value="Orange"/>
</DataTrigger.Setters>
</DataTrigger>
<DataTrigger Binding="{Binding Start}" Value="True">
<DataTrigger.Setters>
<Setter Property="Fill" Value="Red"/>
</DataTrigger.Setters>
</DataTrigger>
</Style.Triggers>
</Style>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<Button Content="Change" Command="{Binding ChangeCommand}"/>
<Rectangle Style="{StaticResource rec}" Grid.Row="1"/>
</Grid>
在DataTrigger中绑定到附加属性,需要显示指定RelativeSource.
<Style TargetType="Button" x:Key="btnStyle">
<Style.Triggers>
<MultiDataTrigger>
<MultiDataTrigger.Conditions>
<Condition
Binding="{Binding (local:ButtonHelper.Num),
RelativeSource={RelativeSource Self}}" Value="1"/>
</MultiDataTrigger.Conditions>
<MultiDataTrigger.EnterActions>
<BeginStoryboard Name="storyBoard2">
<Storyboard Storyboard.TargetProperty="Width">
<DoubleAnimation Duration="0:0:5"
From="100" To="200"
RepeatBehavior="Forever"
AutoReverse="True"/>
</Storyboard>
</BeginStoryboard>
</MultiDataTrigger.EnterActions>
<MultiDataTrigger.ExitActions>
<StopStoryboard BeginStoryboardName="storyBoard2"/>
</MultiDataTrigger.ExitActions>
</MultiDataTrigger>
</Style.Triggers>
<Style>
在DataTrigger中可以使用Binding来绑定变量。
<Style TargetType="Button" x:Key="reagentCellStyle">
<Style.Triggers>
<DataTrigger
Binding="{Binding (temphelper:AttachedHelper.UseContext),
RelativeSource={
RelativeSource AncestorType=local:ViewControl,
Mode=FindAncestor}}"
Value="0">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="Button">
<ControlTemplate.Triggers>
<MultiTrigger>
<MultiDataTrigger.Conditions>
//绑定属性和值
<Condition Binding="{Binding Path=PuttingFlow}"
Value="{x:Static bo:PuttingFlow.WaitingPut}" />
</MultiDataTrigger.Conditions>
</MultiTrigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</DataTrigger>
</Style.Triggers>
MultiDataTrigger
MultiDataTrigger可以在Condition中使用Property或Binding来设置条件,使用Binding设置时,需要显示的设置RelativeSource
同时MultiDataTrigger中的Binding可以结合MultiBinding使用
<MultiTrigger>
<MultiDataTrigger.Conditions>
//绑定属性和值
<Condition Binding="{Binding Path=PuttingFlow}"
Value="{x:Static bo:PuttingFlow.WaitingPut}" />
</MultiDataTrigger.Conditions>
</MultiTrigger>
如果需要在Condition中判断不等于或者大于,小于等情况,则需要通过ValueConverter来转换为bool来实现。