我想知道是否可以将某些控件的样式与 WPF 中的自定义窗口相关联。
下面是方案 - 我创建了一个自定义窗口,并为将在此窗口中使用的许多控件定义了样式。 它们包含在可移植类库中。
问题是我只希望控件在自定义窗口中使用时使用库中的样式(应用程序中有几个不同的窗口(。
我知道我可以为样式分配一个键,并使用包语法从应用程序的 app.xaml 中的可移植库中加载它们,例如:
<Application.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="pack://application:,,,/Custom.Application.Library.Controls;component/Styles/CheckBox.xaml"/>
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
</Application.Resources>
然后在我的自定义窗口中添加控件并设置其样式,如下所示:
<CheckBox x:Name="checkBox" Style="{StaticResource SpecialCheckBox}"
但我真正想做的是在我的类库中定义它们的样式,而无需键,如下所示:
<Style TargetType="{x:Type CheckBox}">
取而代之的是:
<Style x:Key="SpecialCheckBox" TargetType="{x:Type CheckBox}">
这样,当在我的自定义窗口中使用此复选框时,它会自动继承样式。 如果我像这样定义样式,并将其加载到我的 app.xaml 中,问题显然是所有复选框都将继承此样式,而不仅仅是我的自定义窗口中使用的复选框。
因此,我试图找出的是,是否有任何方法可以将样式资源与自定义窗口显式关联,以便我可以在没有键的情况下定义样式,并让它们在自定义窗口中使用时默认继承"特殊"样式,但在应用程序的任何其他窗口中使用 WPF 默认值。 有人有这方面的经验吗?
为清楚起见,这是我的自定义窗口的代码:
XAML:
<!-- Window style -->
<Style TargetType="{x:Type Controls:CCTApplicationWindow}">
<Setter Property="WindowStyle" Value="None"/>
<Setter Property="AllowsTransparency" Value="True"/>
<Setter Property="ResizeMode" Value="CanResizeWithGrip"/>
<Setter Property="MinWidth" Value="500"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type Controls:CCTApplicationWindow}">
<Border BorderBrush="#FF999999">
<Border.Style>
<Style TargetType="{x:Type Border}">
<Setter Property="BorderThickness" Value="1"/>
<Style.Triggers>
<DataTrigger Binding="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=WindowState}" Value="Maximized">
<Setter Property="BorderThickness" Value="7"/>
</DataTrigger>
</Style.Triggers>
</Style>
</Border.Style>
<Grid>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="29"/>
<RowDefinition />
</Grid.RowDefinitions>
<Controls:CCTApplicationHeader Grid.Row="0"
Margin="0"
Title="{TemplateBinding Title}"
DragMoveCommand="{TemplateBinding DragMoveCommand}"
MaximizeCommand="{TemplateBinding MaximizeCommand}"
MinimizeCommand="{TemplateBinding MinimizeCommand}"
CloseCommand="{TemplateBinding CloseCommand}"/>
<Grid Background="White" Grid.Row="1" Margin="0">
<AdornerDecorator>
<ContentPresenter/>
</AdornerDecorator>
</Grid>
</Grid>
</Grid>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
.CS:
public partial class CCTApplicationWindow : Window
{
public static readonly DependencyProperty MaximizeCommandProperty = DependencyProperty.Register("MaximizeCommand", typeof(DelegateCommand), typeof(CCTApplicationWindow));
public static readonly DependencyProperty MinimizeCommandProperty = DependencyProperty.Register("MinimizeCommand", typeof(DelegateCommand), typeof(CCTApplicationWindow));
public static readonly DependencyProperty CloseCommandProperty = DependencyProperty.Register("CloseCommand", typeof(DelegateCommand), typeof(CCTApplicationWindow));
public static readonly DependencyProperty DragMoveCommandProperty = DependencyProperty.Register("DragMoveCommand", typeof(DelegateCommand), typeof(CCTApplicationWindow));
public CCTApplicationWindow()
{
MaximizeCommand = new DelegateCommand(MaximizeExecute);
MinimizeCommand = new DelegateCommand(MinimizeExecute);
CloseCommand = new DelegateCommand(CloseExecute);
DragMoveCommand = new DelegateCommand(DragMoveExecute);
}
static CCTApplicationWindow()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(CCTApplicationWindow), new FrameworkPropertyMetadata(typeof(CCTApplicationWindow)));
}
public DelegateCommand MaximizeCommand
{
get
{
return (DelegateCommand)GetValue(MaximizeCommandProperty);
}
set
{
SetValue(MaximizeCommandProperty, value);
}
}
public DelegateCommand MinimizeCommand
{
get
{
return (DelegateCommand)GetValue(MinimizeCommandProperty);
}
set
{
SetValue(MinimizeCommandProperty, value);
}
}
public DelegateCommand CloseCommand
{
get
{
return (DelegateCommand)GetValue(CloseCommandProperty);
}
set
{
SetValue(CloseCommandProperty, value);
}
}
public DelegateCommand DragMoveCommand
{
get
{
return (DelegateCommand)GetValue(DragMoveCommandProperty);
}
set
{
SetValue(DragMoveCommandProperty, value);
}
}
private void MaximizeExecute(object obj)
{
if (this.WindowState != WindowState.Maximized)
{
this.WindowState = WindowState.Maximized;
}
else
{
SystemCommands.RestoreWindow(this);
}
}
private void MinimizeExecute(object obj)
{
SystemCommands.MinimizeWindow(this);
}
private void CloseExecute(object obj)
{
SystemCommands.CloseWindow(this);
}
private void DragMoveExecute(object obj)
{
DragMove();
}
}
是的,你可以这样做,但你不应该这样做!你已经将这个问题标记为MVVM,但你的架构设计完全破坏了MVVM。MVVM 的全部意义在于视图逻辑包含在视图模型层中;您的视图模型是应该跟踪逻辑层次结构的模型,它们是应该向视图公开属性以控制其外观的模型。换句话说,仅仅因为 XAML 足够灵活和强大来实现此类逻辑并不意味着它是实际执行此操作的最佳位置!
不过,要回答您的问题,是的,这可以通过 DataTrigger 绑定到具有 ObjectToTypeConverter 的父级来完成。下面是将 TextBlock 背景设置为 KnflowerBlue的示例,除非其直接父级是 Grid,在这种情况下,应将其设置为 PaleGoldenrod:
<StackPanel Orientation="Vertical">
<StackPanel.Resources>
<converters:ObjectToTypeConverter x:Key="ObjectToTypeConverter" />
<Style TargetType="{x:Type TextBlock}">
<Setter Property="Background" Value="CornflowerBlue" />
<Style.Triggers>
<DataTrigger Binding="{Binding Path=Parent, RelativeSource={RelativeSource Mode=Self}, Converter={StaticResource ObjectToTypeConverter}}" Value="{x:Type Grid}">
<Setter Property="Background" Value="PaleGoldenrod" />
</DataTrigger>
</Style.Triggers>
</Style>
</StackPanel.Resources>
<Grid Width="100" Height="32" HorizontalAlignment="Left">
<TextBlock Text="TextBox A" /> <!-- Gets a PaleGoldenrod background -->
</Grid>
<Canvas Width="100" Height="32" HorizontalAlignment="Left">
<TextBlock Text="TextBox B" /> <!-- Gets a CornflowerBlue background -->
</Canvas>
</StackPanel>
这是转换器代码。值得指出的是,如果您乐于简单地检查给定类型的父级是否存在于层次结构中的某个位置(而不是直接父级(,那么您甚至不需要它,您可以尝试绑定到 RelativeSource,并将 AncestorType 设置为相关的父类型。
// based on http://stackoverflow.com/questions/8244658/binding-to-the-object-gettype
[ValueConversion(typeof(object), typeof(Type))]
public class ObjectToTypeConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
return value == null ? null : value.GetType();
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
throw new InvalidOperationException();
}
}
但是,我再次恳求您,如果您真的想遵守MVVM,请不要这样做!这正是"适当的"MVVM 旨在解决的问题。
最简单的方法是为Custom
窗口创建单独的ResourceDictionary
。并使用XAML
使用它或使用Code
加载它。