65.9K
CodeProject 正在变化。 阅读更多。
Home

Silverlight 2.0 组件开发

starIconstarIconstarIcon
emptyStarIcon
starIcon
emptyStarIcon

3.62/5 (7投票s)

2008年4月25日

CPOL

7分钟阅读

viewsIcon

44736

downloadIcon

626

本文基于我们在一个项目中积累的 Silverlight 经验,希望分享一些知识。

使用 WPF 创建组件

Silverlight 是一个面向 Web 解决方案的前景平台。微软一直在积极开发它,试图取代 Macromedia Flash 的现有地位。互联网上充斥着大量基于 Silverlight 的令人印象深刻的示例。然而,不幸的是,很难具体说明其中涉及的工作量以及可能遇到的陷阱。作为我们实验的对象,我们选择了一个用于设置特定数值范围的控件。稍后您将了解我们最终的成果,以及我们遇到的困难和陷阱。

因此,让我们来明确任务。该控件应提供以下功能:

  1. 从可能的数值范围内选择一个值
  2. 显示选定的值
  3. 显示可能数值的范围
  4. 吸引人的图形设计。

例如,该控件可能看起来像这样

主要类

我们需要能够定义当前值,由 `Value` 属性负责。此外,我们还需要知道最大值、最小值和步长 - `MinValue`、`MaxValue`、`Step`。最好能有一个选项来调整刻度开始和结束的位置,这将由角度来设置 - `StartAngle`、`SweepAngle`。为了设置边框、刻度和其他项,我们将使用模板。

首先,我们创建基本的 `Indicator` 类,包含最少必需的功能:`MinValue`、`MaxValue`、`Step`。我们继承自 `Control` 类。然后,我们创建 `CircularIndicator` 类,继承自 `Indicator` 类,并在其中定义以下属性:`StartAngle`、`SweepAngle`、`PointerAngle`。`PointerAngle` 将用于绑定。这里我们还将定义鼠标事件的处理程序;我们需要响应鼠标点击、移动以改变指针的位置。

在这里,我们应该指出,在 Silverlight 2.0 中,无法将 `OnMouseEnter`、`OnMouseMove` 等方法定义为类成员。相反,我们必须订阅相应的事件,这有点不寻常,但并不会造成任何不便。

计算 `PointerAngle` 的公式相对简单

StartAngle + Value * SweepAngle / (MaxValue - MinValue)

基本上就这些了,我们只需要添加更改属性的事件。但是,这里有一个技巧。问题在于,与 WPF 不同,Silverlight 2.0 中的 `Dependency` 属性只能指向 4 个参数,这显然不够。缺少设置元数据或回调函数(例如,在本例中检查属性很有用,`CoerceValueCallback`)的选项。

创建用于刻度显示的面板

让我们转向视觉部分。第一个遇到的问题是如何显示刻度。很明显,可以创建一个面板,并在其中放置 15 个矩形作为刻度线,以一定角度排列。然而,这并不是一个吸引人的解决方案。更简单优雅的方法是定义一个继承自 `Panel` 类的类,称之为 `CircularElementPanel`,通过它来定义数量、元素模板、初始和最终角度以及圆的半径。这将允许自动创建元素。显然,该类也适用于创建刻度的标题,只需添加 `StartValue` 和 `EndValue`。然而,这并不那么容易,因为我们还必须在模板中绑定某些内容来显示值。

我们是这样做的

首先,我们创建了 `DataField` 类。

public class DataField
{
private object value = null;

public object ElementValue
{
get { return this.value; }
set { this.value = value; }
} 
public DataField(object value)
{
this.value = value;
}
}  

在创建单独的元素时,`DataField` 对象将被分配给其 `DataContext` 属性。下面提供了一个示例代码

private static void RecreateElements(CircularElementsPanel panel)
{
DataTemplate elementTemplate = panel.ElementsTemplate;
if (elementTemplate == null) return; 
double value = panel.StartValue;
double valueStep = (panel.EndValue - panel.StartValue) / (panel.ElementsCount - 1); 
panel.Children.Clear(); 
for (int i = 0; i < panel.ElementsCount; i++)
{
UIElement element = (UIElement)panel.ElementsTemplate.LoadContent(); 
if (element is FrameworkElement) 
{
((FrameworkElement)element).DataContext = new DataField(value + i * valueStep);
}
panel.Children.Add(element);
}
} 

然后在元素的模板中,可以创建如下所示的绑定

<c:CircularElementsPanel.ElementsTemplate>
   <DataTemplate>
       <TextBlock Width=”50? TextAlignment=”Center” Text=”{Binding ElementValue}”/>
   </DataTemplate>
</c:CircularElementsPanel.ElementsTemplate>

这些元素会沿着圆周排列吗?为了实现这种效果,我们需要在我们的面板上预先定义 `ArrangeOverride` 和 `MeasureOverride` 方法。我们对其进行了简化——使用了 CodeProject 的 `PolarPanel`,它预定义了这些方法并从中继承。尽管它是为 WPF 编写的,但没有问题,唯一需要重新加工的是属性注册。然而,我们应该指出,该面板还包含两个附加属性——`angle` 和 `radius`。`Radius` 将被设置为可能的最大值(考虑到最终面板大小),而 `angle` 将在 `RecreateElements` 方法中定义。考虑到最新的更改,该函数如下所示

private static void RecreateElements(CircularElementsPanel panel)
{
DataTemplate elementTemplate = panel.ElementsTemplate; 
if (elementTemplate == null) return; 
double angle = panel.StartAngle;
double angleStep = panel.SweepAngle / (panel.ElementsCount - 1); 
double value = panel.StartValue;
double valueStep = (panel.EndValue - panel.StartValue) / (panel.ElementsCount - 1); 
panel.Children.Clear(); 
for (int i = 0; i < panel.ElementsCount; i++)
{
UIElement element = (UIElement)panel.ElementsTemplate.LoadContent();
SetRadius(element, panel.ElementsRadius);
SetAngle(element, angle + i * angleStep);
if (element is FrameworkElement)
{    
((FrameworkElement)element).DataContext = new DataField(value + i * valueStep);
}
panel.Children.Add(element);
}    
} 

最后,让我们创建另一个类——`CircularGauge` 或 `Gauge`。在这个类中,只有一个属性——旋钮转动的角度。更改属性时,我们应用 `RotateTransform`。

创建控件模板

以下是 `CircularIndicator` 的完整模板。这应该有助于解决稍后描述的问题。

<ControlTemplate x:Key=”Indicator” TargetType =”w:CircularIndicator”>
            <Grid x:Name=”LayoutRoot” Margin=”0?>
                <Rectangle RadiusX=”5? RadiusY=”5? Margin=”0? Stroke=”Black”>
                    <Rectangle.Fill>
                        <LinearGradientBrush StartPoint=”0,0? EndPoint=”0,1?>
                            <LinearGradientBrush.GradientStops>
                                <GradientStop Color=”Lavender” Offset=”0?/>
                                <GradientStop Color=”Gray” Offset=”0.1?/>
                                <GradientStop Color=”LightGray” Offset=”0.5?/>
                                <GradientStop Color=”Gray” Offset=”0.9?/>
                                <GradientStop Color=”Lavender” Offset=”1?/>
                            </LinearGradientBrush.GradientStops>
                        </LinearGradientBrush>
                    </Rectangle.Fill>
                </Rectangle>
                <c:CircularElementsPanel x:Name=”ScaleLabels”
StartValue=”{TemplateBinding MinValue}” 
EndValue=”{TemplateBinding MaxValue}” Margin=”50?
StartAngle=”{TemplateBinding StartAngle}”
SweepAngle=”{TemplateBinding SweepAngle}”
ElementsCount=”{TemplateBinding ValuesCount}”
RotateElements=”False”>
                    <c:CircularElementsPanel.ElementsTemplate>
                        <DataTemplate>
                            <TextBlock Width=”50? TextAlignment=”Center”
Text=”{Binding ElementValue}”/>
                        </DataTemplate>
                    </c:CircularElementsPanel.ElementsTemplate>
                </c:CircularElementsPanel>
                <c:CircularElementsPanel StartValue=”{TemplateBinding MinValue}”
EndValue=”{TemplateBinding MaxValue}”
Margin=”80? StartAngle=”{TemplateBinding StartAngle}”
SweepAngle=”{TemplateBinding SweepAngle}”
ElementsCount=”{TemplateBinding ValuesCount}”
RotateElements=”True”>
                    <c:CircularElementsPanel.ElementsTemplate>
                        <DataTemplate>
                            <Rectangle Fill=”Black” Width=”5? Height=”2?/>
                        </DataTemplate>
                    </c:CircularElementsPanel.ElementsTemplate>
                </c:CircularElementsPanel>
                <w:CircularGauge Margin=”90? Template=”{StaticResource Gauge}”
Angle=”{TemplateBinding PointerAngle}”/> 
</Grid>
</ControlTemplate> 

首先看到的是带有渐变填充的背景层,然后是刻度和它的标题,最后是仪表盘。请注意刻度线的数量绑定到 `ValuesCount` 属性。这可以轻松计算

((MaxValue - MinValue) / Step) + 1;

绑定表达式的赋值

将标题和刻度线的数量与 `ValuesCount` 绑定会很好,但是没有这样的可能性。老实说,我们仍然不敢相信,我们在搜索设置 `a*b` 类型绑定表达式的解决方案上花费的时间没有取得任何结果。

我们想提请您注意的第二项是 `Gauge`。你可能会问,这是做什么用的?实际上,它的模板由两个椭圆组成。为什么不在模板中创建 `RotateTransform` 并将角度值绑定到 `PointerAngle` 属性值?然而,我们不知何故做不到。尽管 `Angle = “{TemplateBinding PointerAngle}”` 表达式在编译时不会报错,但也没有带来任何积极的结果。为什么?这仍然是个谜。

因此,这就是创建 `CircularGauge` 类的原因。还值得注意的是,在 WPF 中,我们可以使用 Binding 的 `RelativeSource` 属性轻松解决此任务,但在 Silverlight 2.0 中它不存在。我们希望它能在后续版本中出现,因为没有它,很难创建实质性的东西。

此外,`RelativeSource` 的存在也将允许绕过设置绑定表达式的问题。`Converter` 可以通过某个对象参数化。该对象将定义操作和其中一个操作数;第二个操作数将由绑定字段定义。不幸的是,`TemplateBinding` 中没有 `Converter` 属性,因此无法通过它来解决问题。

动画

最后,第三个方面是动画。如果在 WPF 中,我们可以在控件模板中创建一组触发器,例如在鼠标悬停时,然后从那里开始动画(`Storyboard`),但这里有另一种方法。借助 `TemplatePart` 属性,我们定义了应包含在类控件模板中的元素的名称和类型。在这里,我们可以设置用于动画的 `Storyboard` 名称以及资源中存储 `Storyboard` 的元素的名称。

[TemplatePart(Name = “RootElement”,
    Type = typeof(FrameworkElement))] [TemplatePart(Name = “Normal State”,
    Type = typeof(Storyboard))] 

此外,在构造函数中,我们订阅 `MouseEnter` 和 `MouseLeave` 事件,并创建以下处理程序

void CircularGauge_MouseLeave(object sender, MouseEventArgs e)
{
    FrameworkElement panel = this.GetTemplateChild(“RootElement”) as FrameworkElement;
    (panel.Resources[“MouseOver State”] as Storyboard).Stop();
}
void CircularGauge_MouseEnter(object sender, MouseEventArgs e)
{
FrameworkElement panel = this.GetTemplateChild(“RootElement”) as FrameworkElement;
(panel.Resources[“MouseOver State”] as Storyboard).Begin();
}

结果是,当鼠标指向控件时,动画开始;当鼠标移开时,动画停止。动画在我们的示例中是在 `Gauge` 上实现的。那么 `Storyboard` 看起来是这样的

<Storyboard x:Key=’MouseOver State’>
<ColorAnimation Storyboard.TargetName=’Stop’ Storyboard.TargetProperty=’Color’
    To=’White’ Duration=”0:0:0.5? AutoReverse=’False’/>
</Storyboard>

摘要

还应该注意到,尽管在 Silverlight 开发过程中存在所有的小故障和困难,但这无疑是用户友好且外观精美的 Web 界面交互的一大进步。当然,这项技术仍在发展中,但开发者的工具尚未调试完善(几乎所有错误都会收到相同的消息,并且经常陷入无限循环),并且与 WPF 相比,自定义选项很少或几乎没有(模板中的触发器、绑定、动画)。然而,为 Web 创建管理元素变得更加容易,此外,Silverlight 技术在编程和图形设计之间划清了界限。

历史

  • 2008 年 4 月 25 日:首次发布
© . All rights reserved.