是否可以在WPF中为通用基本控件提供默认样式?
假设我有以下基类:
public abstract class View<T> : ContentControl
where T : ViewModel
{
static View()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(View<T>),
new FrameworkPropertyMetadata(typeof(View<T>)));
}
// Other properties, methods, etc in here
}
public abstract class ViewModel
{
// Other properties, methods, etc in here
}
然后假设我有两个继承自这些基类的类:
public partial class TestView : View<TestViewModel>
{
public TestView()
{
InitializeComponent();
}
// TestView specific methods, properties, etc
}
public class TestViewModel : ViewModel
{ /* TestViewModel specific methods, properties, etc */ }
现在我想为所有派生控件使用的基本控件提供默认样式:
<Style TargetType="{x:Type local:View`1}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type local:View`1}">
<Border Background="Magenta"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}">
<StackPanel>
<Button>Test</Button>
<ContentPresenter ContentSource="Content" />
</StackPanel>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
但是,当我使用TestView控件时,我没有应用模板标记(因此我在TestView控件的XAML中定义的任何内容都不在visual / logical树中)。
我基本上尝试使用我的基本视图/ viewmodel类并应用一致的外观。这当然适用于非通用基本视图案例。但是,我需要在视图和视图模型之间进行类型安全连接,这样我就可以从视图中调用视图模型上的方法(我知道这些方法可能与某些人已经实现MVVM的方式“坐得不好”)。
答案 0 :(得分:5)
我找到了一个涉及自定义TypeExtension
的相当简单的解决方案。
1 - 将DefaultStyleKey设置为默认泛型类型as mentioned in CodeNaked's answer:
DefaultStyleKeyProperty.OverrideMetadata(typeof(View<T>),
new FrameworkPropertyMetadata(typeof(View<>)));
2 - 创建以下类而不是继承自System.Windows.Markup.TypeExtension
[System.Windows.Markup.ContentProperty("Type")]
public class TypeExtension : System.Windows.Markup.TypeExtension
{
public TypeExtension()
: base()
{ }
public TypeExtension(Type type)
: base(type)
{ }
public override object ProvideValue(IServiceProvider serviceProvider)
{
if (Type == null)
throw new InvalidOperationException("Must specify the Type");
return Type;
}
}
3 - 更新样式的TargetType以指向新的local:Type
扩展名,而不是通常的x:Type
扩展名
<Style>
<Style.TargetType>
<local:Type Type="{x:Type local:View`1}" />
</Style.TargetType>
<Setter Property="Control.Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type Control}">
. . .
多数民众赞成......
但是有一点需要注意,当您尝试绑定/设置View&lt; T&gt;上定义的任何依赖项属性时,VS会抛出编译错误。类。所以你不能使用像{TemplateBinding ViewTProperty}
...
答案 1 :(得分:1)
简答:没有
答案很长:
在您的代码中,您指定了typeof(View<T>)
的DefaultStyleKey,其中T被解析为实际类型。在XAML中,您实际上正在typeof(Value<>)
,其中T仍然是“未定义”。
您可以将DefaultStyleKey设置为:
DefaultStyleKeyProperty.OverrideMetadata(typeof(View<T>),
new FrameworkPropertyMetadata(typeof(View<>)));
这将正确找到样式,但会导致异常(因为TestView
不能出现View<>
)。
最好的办法是像你一样定义基本样式,但是给它一个x:Key,如“ViewBaseStyle”。然后为每个派生类型创建一个Style,它是BasedOn ViewBaseStyle。
答案 2 :(得分:0)
我这样做的方式是我创建了一个没有泛型的基类并模板化了。然后我继承了一个泛型类(不是模板化)的基类,它可以用来制作你的类变体(也没有模板化)。实际上,继承基类(没有泛型)的所有内容都将具有相同的模板。
例如,
//You can define a template for this!
public class ViewBase : UserControl
{
public ViewBase() : base()
{
DefaultStyleKey = typeof(ViewBase);
}
}
//Use for class variations
public class View<T> : ViewBase
{
public View() : base()
{
//Do whatever
}
}
//Example class variation
public class DecimalView : View<decimal>
{
public DecimalView() : base()
{
//Do whatever
}
}
ViewBase
,View<T>
和DecimalView
现在都拥有相同的默认样式。此外,您还可以根据原始样式(ViewBase
)为每个类变体指定单独的样式,而不是针对泛型类。
值得注意的是,绑定到顶级类属性的最佳方法是使用语法{Binding Path, RelativeSource={RelativeSource AncestorType={x:Type ViewBase}}}
与{TemplateBinding Path}
。后者以及{Binding Path, RelativeSource={RelativeSource TemplatedParent}}
仅适用于ViewBase
拥有的媒体资源。