覆盖 WPF 控件
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/17194333/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Overriding a WPF control
提问by user1836155
I have a WPF control ParentWPFControl from a third party that I would like to inherit from (let's call the child class ChildWPFControl). In the process, I plan to override some of the back-end logic and parts of the front end styles. I can do the former just fine but I have problems doing the latter.
我有一个来自第三方的 WPF 控件 ParentWPFControl,我想从它继承(我们称之为子类 ChildWPFControl)。在这个过程中,我打算覆盖一些后端逻辑和前端样式的部分。我可以做前者很好,但我做后者有问题。
I attempt to use a xaml <-> xaml.cs structure for the child country, but that appears to be not allowed with the following warning from VS:
我尝试对子国家/地区使用 xaml <-> xaml.cs 结构,但这似乎是不允许的,VS 发出以下警告:
Partial declarations of 'ChildWPFControl' must not specify different base classes
Now, I suppose I can write a ResourceDictionary XAML and define the front end there, but that becomes a problem if I want to add event handlers to the XAML (at least I couldn't find a way to do that)
现在,我想我可以编写一个 ResourceDictionary XAML 并在那里定义前端,但是如果我想向 XAML 添加事件处理程序,这就会成为一个问题(至少我找不到办法做到这一点)
Another alternative I have is to define the override template directly in the objects that use the ChildWPFControl but that makes the design less modular.
我的另一种选择是直接在使用 ChildWPFControl 的对象中定义覆盖模板,但这会降低设计的模块化程度。
A final alternative I can think of is to make a xaml <-> xaml.cs pair that is a XAML style container and then force the ChildWPFControl to use the ControlTemplate defined within through the back end event handler.
我能想到的最后一个替代方法是创建一个 xaml <-> xaml.cs 对,它是一个 XAML 样式容器,然后强制 ChildWPFControl 使用通过后端事件处理程序定义的 ControlTemplate。
Anyway, what I am looking for is an elegant and modular solution for my problem. Any advice would be welcomed.
无论如何,我正在寻找的是针对我的问题的优雅且模块化的解决方案。任何建议将受到欢迎。
Thanks
谢谢
回答by dowhilefor
There are a couple of steps necessary to completely override a WPF Control. Some are necessary some are optional depending on your needs. I will explain the two important ones for you:
完全覆盖 WPF 控件需要几个步骤。有些是必需的,有些是可选的,具体取决于您的需要。我将为您解释两个重要的:
Creating a new default style
创建新的默认样式
Every WPF control has somewhere a default style which contains it visual representation and override properties. Now if you derive from control WPF still thinks you want to use this default style, to change that you change the DefaultStyle in a static constructor like this
每个 WPF 控件都有一个默认样式,其中包含它的可视化表示和覆盖属性。现在,如果您从控件派生 WPF 仍然认为您想要使用此默认样式,要更改您在静态构造函数中更改 DefaultStyle,如下所示
class MyButton : Button
{
static MyButton()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(MyButton), new FrameworkPropertyMetadata(typeof(MyButton)));
}
}
Now if you use MyButton WPF tries to find a Style for MyButton, not for Button. OverridesDefaultStyle is a property in a style which might also be handy at some points. Usually these default styles should be placed in a theme related xaml.
现在,如果您使用 MyButton,WPF 会尝试为 MyButton 而不是 Button 查找样式。OverridesDefaultStyle 是样式中的一个属性,在某些时候可能也很方便。通常这些默认样式应该放在与主题相关的 xaml 中。
Event Handlers when overriding classes
覆盖类时的事件处理程序
It is correct in a ControlTemplateor Styleyou can't use the syntactic sugar of using event like Click="OnClick". The point is, that the visual representation is decoupled from the logic part as much as possible. There are other ways though to overcome this, using the OnApplyTemplate method. By overriding this you ask the template "Give me this control" and then you just add your events there.
在 a 中是正确的, ControlTemplate或者Style您不能使用使用事件的语法糖,例如Click="OnClick". 关键是,视觉表示尽可能与逻辑部分分离。还有其他方法可以克服这个问题,使用 OnApplyTemplate 方法。通过覆盖它,您可以询问模板“给我这个控制”,然后您只需在那里添加您的事件。
override OnApplyTemplate()
{
var innerChild = Template.FindName("PART_InnerChild", this) as MyInnerControl;
if(innerChild != null)
innerChild.SomeEvent += OnSomeEvent;
}
Note: The name of these controls usually begin with a PART_ by convention, this can be seen in WPF basic controls aswell. Its a nice way to tell the designers "Without this control, the logic part might break". There is also the attribute TemplatePartbut it is not really important, WPF doesn't care about it. AFAIK Expression blend does some with it, personally i use it to tell other people what kind of inner controls are absolutely necessary to make this control work.
注意:按照惯例,这些控件的名称通常以 PART_ 开头,这也可以在 WPF 基本控件中看到。这是告诉设计人员“没有这种控制,逻辑部分可能会中断”的好方法。还有属性TemplatePart但这并不重要,WPF 不关心它。AFAIK Expression blend 用它做了一些,我个人用它来告诉其他人什么样的内部控件是绝对必要的,以使这个控件工作。
Personal advice
个人建议
Deriving from a class is usually the last step we do when trying to customize controls. Because a lot of work is necessary to fully make it work and it can be limiting in reusability, we try to avoid it, for example a good alternatives are besides template overriding and styling; attached behaviors.
从类派生通常是我们尝试自定义控件时所做的最后一步。因为需要做很多工作才能让它完全工作,而且它可能会限制可重用性,所以我们尽量避免它,例如,除了模板覆盖和样式之外,还有一个很好的选择;附加行为。
Lastly, The whole subject is covered in a nice MSDNarticle.
最后,整个主题都包含在一篇不错的 MSDN文章中。
Hope that helps
希望有帮助
回答by Frank59
You can create your user control as wrapper, containing base control. In this way you can change styles in xaml add some logic in C# for wrapped contrоl. But it's tediously process.
您可以将用户控件创建为包含基本控件的包装器。通过这种方式,您可以在 xaml 中更改样式,在 C# 中添加一些用于包装控制的逻辑。但是过程很繁琐。
Edit:adding sample(wrapper for telerik:RadComboBox )
编辑:添加示例(用于 Telerik:RadComboBox 的包装器)
XAML:
XAML:
<UserControl x:Class="Controls.SingleDictionaryValueSelector"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:CardControls="clr-namespace:Controls"
xmlns:telerik="http://schemas.telerik.com/2008/xaml/presentation" MinWidth="150" MinHeight="25" >
<Grid >
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"></ColumnDefinition>
<ColumnDefinition Width="25"></ColumnDefinition>
</Grid.ColumnDefinitions>
<!-- customize visual for wrapped control -->
<telerik:RadComboBox x:Name="cb"
Grid.Column="0"
VerticalAlignment="Center"
SelectedValuePath="Key"
ClearSelectionButtonContent="Clear"
ClearSelectionButtonVisibility="Visible"
CanAutocompleteSelectItems="True"
CanKeyboardNavigationSelectItems="True"
SelectAllTextEvent="None"
OpenDropDownOnFocus="false"
IsFilteringEnabled="True"
TextSearchMode="Contains"
EmptyText="Select item"
telerik:StyleManager.Theme="Metro"
FontFamily="Calibri"
FontSize="14"
IsEditable="True"
Foreground="#666"
KeyDown="cb_KeyDown"
SelectionChanged="cb_SelectionChanged"
GotMouseCapture="cb_GotMouseCapture"
DropDownOpened="cb_DropDownOpened"
KeyUp="cb_KeyUp">
<telerik:RadComboBox.ItemTemplate>
<DataTemplate>
<TextBlock TextWrapping="Wrap" Width="{Binding RelativeSource={RelativeSource AncestorType=telerik:RadComboBox},Path=ActualWidth}" Text="{Binding Path=Value}" />
</DataTemplate>
</telerik:RadComboBox.ItemTemplate>
</telerik:RadComboBox>
<CardControls:ErrorInfo x:Name="errorInfoControl" Grid.Column="1" Visibility="Hidden"></CardControls:ErrorInfo>
</Grid>
</UserControl>
CS:
CS:
public partial class SingleDictionaryValueSelector : IMyCustomInterface
{
....
private void cb_KeyDown(object sender, KeyEventArgs e)
{
RadComboBox senderCombo = sender as RadComboBox;
...
}
private void cb_KeyUp(object sender, KeyEventArgs e)
{
SearchExecute();
}
private void cb_SelectionChanged(object sender, System.Windows.Controls.SelectionChangedEventArgs e)
{
RadComboBox senderCombo = sender as RadComboBox;
...
}
private void cb_DropDownOpened(object sender, EventArgs e)
{
...
}
...
}
回答by Sogger
It looks like you have your inheritance mixed up more than that it is not allowed. Your root element of your xaml must match the base class of your xaml.cs.
看起来您的遗产混杂得比不允许的多。xaml 的根元素必须与 xaml.cs 的基类匹配。
If you are defining the base class in the same project, you will not be able to use it as the base class in the xaml, because it itself is still xaml and not a compiled control yet. Some ways to solve this: You can compile it in a seperate project and reference it, you can compile the base class entirely in .cs instead of a partial class, or you can use some style wizardry. Here is a link with examples of the last two: http://svetoslavsavov.blogspot.ca/2009/09/user-control-inheritance-in-wpf.html
如果你在同一个项目中定义基类,你将无法在xaml中使用它作为基类,因为它本身仍然是xaml,还不是一个已编译的控件。解决这个问题的一些方法:您可以在单独的项目中编译它并引用它,您可以完全在 .cs 中编译基类而不是部分类,或者您可以使用一些样式向导。这是后两个示例的链接:http: //svetoslavsavov.blogspot.ca/2009/09/user-control-inheritance-in-wpf.html

