第3章 自定义控件2
3 强大的附加属性
所以你正在构建一个令人惊喜的新应用程序,你需要一种在现有控件中没有直接支持的行为。你确信除了创建子类并为自己创建一堆工作外,没有其他方法来扩展现有的控件功能。是时候创建子类了,对吗?
WPF提供了一项创新功能,称为附加属性,它可以用于向现有控件添加行为。这些新属性不是在正在扩展的控件上定义的,而是在一个单独的对象上定义的(通常是DependencyObject)。因此,我们得到一个定义附加属性的源对象和一个将这些属性附加到其上的目标对象。目标对象是在源中包含的新功能扩展的对象。当在目标对象上设置附加属性时,源会触发属性更改事件。事件处理程序会传递有关目标以及属性的新旧值的信息。
通过连接属性更改处理程序,我们可以通过调用目标上的方法、更改属性和侦听事件来添加其他行为。在第6章“附加属性的力量”中,我们会更详细地介绍使用附加属性的例子。请记住,附加属性只是扩展功能的一种手段;原始目标控件仍然需要被视为“黑盒子”,不能修改其内部。
假设您想表示特定工作流程的进度。虽然进度在内部以百分比表示,但用户界面指南要求以阶段方式显示进度,共有五个阶段,每个阶段表示完成100%的1/20。
这可以通过使用进度条的控制模板(ControlTemplate)和附加属性的有趣用法的组合来实现。控制模板定义了ProgressBar的可视化效果。触发器用于突出显示当前阶段。由于触发器仅仅能够使用单个值进行比较(例如,当PropertyX等于ValueY时触发),而不是使用范围比较,我们不能直接使用ProgressBar的Value属性来突出显示当前阶段。
一个很好的方法是实际上增强ProgressBar,使其具有一个Stage属性。这将使控件扩展的其余部分变得更容易。为了做到这一点,我们需要一个帮助类ProcessStageHelper,它将进度值的范围映射到自定义的ProcessStage枚举。我们根据当前的进度值选择正确的ProcessStage。可以在代码清单3.1中看到这一点。
ProcessStageHelper类公开了两个附加属性:一个用于接收进度值,另一个用于提供一个只读的ProcessStage枚举值。
public class ProcessStageHelper : DependencyObject
{
public static readonly DependencyProperty
ProcessCompletionProperty = DependencyProperty.RegisterAttached(
”ProcessCompletion”, typeof(double),
typeof(ProcessStageHelper), new PropertyMetadata(0.0,
OnProcessCompletionChanged));
private static readonly DependencyPropertyKey
ProcessStagePropertyKey =
DependencyProperty.RegisterAttachedReadOnly(
”ProcessStage”, typeof(ProcessStage),
typeof(ProcessStageHelper),
new PropertyMetadata(ProcessStage.Stage1));
public static readonly DependencyProperty ProcessStageProperty =
ProcessStagePropertyKey.DependencyProperty;
private static void OnProcessCompletionChanged(DependencyObject d,
DependencyPropertyChangedEventArgs e)
{
double progress = (double)e.NewValue;
ProgressBar bar = (d as FrameworkElement).TemplatedParent
as ProgressBar;
if (progress >= 0 && progress < 20)
bar.SetValue(ProcessStagePropertyKey, ProcessStage.Stage1);
if (progress >= 20 && progress < 40)
bar.SetValue(ProcessStagePropertyKey, ProcessStage.Stage2);
if (progress >= 40 && progress < 60)
bar.SetValue(ProcessStagePropertyKey, ProcessStage.Stage3);
if (progress >= 60 && progress < 80)
bar.SetValue(ProcessStagePropertyKey, ProcessStage.Stage4);
if (progress >= 80 && progress <= 100)
bar.SetValue(ProcessStagePropertyKey, ProcessStage.Stage5);
}
前面的代码比必要的复杂,只是为了帮助说明可以向现有类添加的额外功能。OnProcessCompletionChanged处理程序设置了ProcessStage只读附加属性。由于这个只读属性附加到了ProgressBar类,因此可以在ControlTemplate.Triggers部分使用它来根据当前工作流程阶段提供自定义的UI,如代码清单3.2所示。
<ControlTemplate x:Key=”ProcessStageTemplate” TargetType=”ProgressBar”> ... ... <ControlTemplate.Triggers> <Trigger Property=”Chapter_CustCtrl:ProcessStageHelper.ProcessStage” Value=”Stage1”> <Setter Property=”Fill” Value=”{StaticResource SelectedStageBrush}” TargetName=”Stage1” /> <Setter Property=”Stroke” Value=”#bb2d00” TargetName=”Stage1” /> </Trigger> <Trigger Property=”Chapter_CustCtrl:ProcessStageHelper.ProcessStage” Value=”Stage2”> <Setter Property=”Fill” Value=”{StaticResource SelectedStageBrush}” TargetName=”Stage2” /> <Setter Property=”Stroke” Value=”#bb2d00” TargetName=”Stage2” /> </Trigger> <Trigger Property=”Chapter_CustCtrl:ProcessStageHelper.ProcessStage” Value=”Stage3”> <Setter Property=”Fill” Value=”{StaticResource SelectedStageBrush}” TargetName=”Stage3” /> <Setter Property=”Stroke” Value=”#bb2d00” TargetName=”Stage3” /> </Trigger> ... ... </ControlTemplate.Triggers> </ControlTemplate>
在3.2清单中,我们创建了多个触发器。每个触发器在流程阶段设置为特定值时被触发。然后,该代码可以设置相应“阶段”框的填充和描边属性。 请记住,这只是一个小例子,展示了您可以在不创建自己的控件的情况下扩展控件行为和视觉效果的方式之一。