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

逐步创建类似 Vista 的忙碌光标 Silverlight 控件

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.61/5 (7投票s)

2008年12月1日

Ms-PL

6分钟阅读

viewsIcon

44291

downloadIcon

382

在本文中,您将学习如何一步一步创建一个类似 Silverlight 的 Vista 忙碌光标控件。

Screen Capture of Busy Icon

引言

在您的 Silverlight 应用程序中,您经常需要下载某些内容或花费一段时间执行某些操作。在此期间,您可能希望显示一个指示器,告诉用户您的应用程序正在忙碌。您可能希望为此创建一个可用于您应用程序的控件。在本文中,我将逐步展示如何创建一个类似 Vista 忙碌光标的控件。

第一部分:创建 Silverlight 应用

我们为什么要先创建一个 Silverlight 应用程序?原因是 Expression Blend 2 (带 SP1) 无法直接设计 Silverlight 控件的默认模板。因此,我们首先使用 Blend 创建一个应用程序来设计控件的视觉效果。

步骤 1.1:在 VS 2008 中创建 Silverlight 应用程序

打开 Visual Studio 2008,创建一个 Silverlight 应用程序。右键单击 Page.xaml 文件,选择“在 Expression Blend 中打开”,然后项目将在 Blend 2 中打开。

步骤 1.2:设计控件的视觉效果

在 Blend 中,Page.xaml 已打开。在“对象和时间轴”面板中,选择 LayoutRoot 元素。双击它会将其置于选中状态。

Select the LayoutRoot

然后,从“工具箱”中选择一个 Grid 控件,双击将其添加到 LayoutRoot。在“属性”面板中,将 GridWidthHeight 设置为 Auto,将 HorizontalAlignmentVerticalAlignment 设置为 Center

Set Grid properties

在“对象和时间轴”面板中,双击刚刚添加的 Grid 会选中它。从“工具箱”面板中将一个 Ellipse 添加到其中。将 WidthHeight 属性设置为 20,将 Fill 设置为 None,将 Stroke 设置为 GradientBrush,并将 StrokeThickness 设置为 6。将 Opacity 设置为 0。

Ellipse Properties

控件的最终视觉效果如下所示:

Final Visual of the Control

步骤 1.3:创建 VisualStates

现在我们创建 VisualStateVisualState“表示控件处于特定状态时的视觉外观”(来自 MSDN)。我们的控件可以处于两种状态之一:“忙碌状态”(`BusyState`) 或“空闲状态”(`IdleState`)。在 BusyState 中,控件将可见并显示动画。在 IdleState 中,控件将隐藏。

在“状态”面板中,单击“添加状态组”按钮添加一个状态组。

Add states group

将“VisualStateGroup”重命名为“BusyIdleStates”。单击“BusyIdleStates”右侧的“添加状态”按钮,添加两个 VisualState 并将其命名为“BusyState”和“IdleState”。

Add two state

在“状态”面板中选择“BusyState”。然后,打开时间轴面板,在“对象和时间轴”面板中,选择 Ellipse 元素。然后,选择 Ellipse 元素的 Stroke 属性。在“工具箱”中,选择“画笔变换”工具,将时间轴移动到“0:00.300”,并使用“画笔变换”工具将 Stroke 画笔旋转 45 度。

Selecting the ellipseThe stroke property

选择“画笔变换”工具并使用它。

The Brush Transform tool

将时间轴移动到“0:00.600”,并使用“画笔变换”工具将画笔旋转 90 度。重复此步骤,直到旋转一整圈。

最后,选择 Opacity 属性,将时间轴移动到“0:00.000”,并将其设置为 100%。这将使控件可见。完成此步骤后,通过单击状态面板中的“Base”来关闭“BusyState”。

步骤 1.4:测试

将以下代码添加到 Page.xaml.cs 文件中。

public Page()
{
   InitializeComponent();
   // add a event handle to Loaded event
   this.Loaded += new RoutedEventHandler(Page_Loaded);
}

void Page_Loaded(object sender, RoutedEventArgs e)
{
    // Go to BusyState
   VisualStateManager.GoToState(this, "BusyState", false);
}

在 VS2008 中右键单击项目,选择“调试”,然后选择“运行新实例”。您将看到应用程序的效果。

第二部分:创建控件

步骤 2.1:我们控件的文件结构

在这一部分,我们将刚刚创建的应用程序转换为一个控件,这样您就可以轻松地在任何应用程序中使用此控件。首先,向当前解决方案添加一个“Silverlight 类库”项目,并将其命名为“WaitingIcon”。将 class1.cs 重命名为 WaitingIcon.cs,还将 class1 重命名为 WaitingIcon,并使其派生自 Control 类。向项目添加一个名为“themes”的文件夹,在该 themes 文件夹中添加一个新文本文件,并将其命名为“generic.xaml”。我们的默认控件模板已准备好在此处可用。

Project Art

选择 Generic.xaml 文件并右键单击它。然后,选择“属性”菜单项。在“属性”窗口中,将“生成操作”设置为“资源”,并删除“自定义工具”框中的文本。

Generic file property

步骤 2.2:创建我们控件的默认控件模板

打开 Generic.xaml 文件,并将以下代码添加到该文件。

<ResourceDictionary  xmlns="http://schemas.microsoft.com/client/2007"
                     xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
                     xmlns:vsm="clr-namespace:System.Windows;assembly=System.Windows" 
                     xmlns:controls="clr-namespace:Cokkiy.Display">       
    <Style TargetType="controls:WaitingIcon">        
        <Setter Property="Template">
            <Setter.Value>
                <--Control Template for the WaitingIcon-->
                <ControlTemplate TargetType="controls:WaitingIcon">   
                </ControlTemplate>
            </Setter.Value>
        </Setter>
    </Style>
</ResourceDictionary>

打开我们在第一部分创建的 Page.xaml 文件,将“<Grid>”和“</Grid>”之间的所有代码复制到 Generic.xaml 文件中,并将其插入到 `<ControlTemplate TargetType="controls:WaitingIcon">` 之后。

<ControlTemplate TargetType="controls:WaitingIcon">                    
    <Grid>                        
        <Ellipse StrokeThickness="{TemplateBinding StrokeThickness}" 
           x:Name="ellipse" 
           Stroke="{TemplateBinding Background}" 
           Opacity="0">
        </Ellipse>
    </Grid>
</ControlTemplate>

在应用程序中,我们将 Ellipse StrokeThickness 直接设置为 6。但在我们的控件中,我们将其设置为 TemplateBinding,以便最终用户可以设置宽度。将 Stroke 属性更改为 TemplatingBinding,以便最终用户可以为我们的控件设置不同的 Brush。我们可以通过在 `<Style TargetType="controls:WaitingIcon">` 之后添加以下代码来为 Stroke 添加默认 Brush,并为 StrokeThickness 属性添加默认宽度。

<Style TargetType="controls:WaitingIcon">
   <Setter Property="StrokeThickness" Value="6"/>
   <Setter Property="Background">
      <Setter.Value>
         <LinearGradientBrush EndPoint="0.5,1" StartPoint="0.5,0">
             <GradientStop Color="#FF0A0E94" Offset="0.576"/>
             <GradientStop Color="#FF0FFF1B" Offset="1"/>
         </LinearGradientBrush>
      </Setter.Value>
   </Setter>

将“VisualStates”定义从 Page.xaml 复制到 Generic.xaml,并将其插入到 `<Grid>` 之后。

<Grid>
   <vsm:VisualStateManager.VisualStateGroups>
         <vsm:VisualStateGroup x:Name="BusyIdleStates">
               <vsm:VisualState x:Name="BusyState">
                    <Storyboard AutoReverse="False" 
                           RepeatBehavior="Forever">
                        <PointAnimationUsingKeyFrames BeginTime="00:00:00" 
                               Storyboard.TargetName="ellipse" 
                               Storyboard.TargetProperty=
                                "(Shape.Stroke).(LinearGradientBrush.StartPoint)">
                            <SplinePointKeyFrame KeyTime="00:00:00.25" 
                               Value="0.868,0.161"/>
                            <SplinePointKeyFrame KeyTime="00:00:00.5" 
                               Value="0.997,0.44"/>
                            <SplinePointKeyFrame KeyTime="00:00:00.75" 
                               Value="0.845,0.863"/>
                            <SplinePointKeyFrame KeyTime="00:00:01" 
                               Value="0.545,0.999"/>
                            <SplinePointKeyFrame KeyTime="00:00:01.2500000" 
                               Value="0.166,0.873"/>
                            <SplinePointKeyFrame KeyTime="00:00:01.5" 
                               Value="0.001,0.536"/>
                            <SplinePointKeyFrame KeyTime="00:00:01.7500000" 
                               Value="0.084,0.222"/>
                            <SplinePointKeyFrame KeyTime="00:00:02" 
                               Value="0.462,0.001"/>
                        </PointAnimationUsingKeyFrames>
                        <PointAnimationUsingKeyFrames BeginTime="00:00:00" 
                               Storyboard.TargetName="ellipse" 
                               Storyboard.TargetProperty=
                                 "(Shape.Stroke).(LinearGradientBrush.EndPoint)">
                             <SplinePointKeyFrame KeyTime="00:00:00.25" 
                                Value="0.132,0.839"/>
                             <SplinePointKeyFrame KeyTime="00:00:00.5" 
                                Value="0.003,0.56"/>
                             <SplinePointKeyFrame KeyTime="00:00:00.75" 
                                Value="0.155,0.137"/>
                             <SplinePointKeyFrame KeyTime="00:00:01" 
                                Value="0.455,0.001"/>
                             <SplinePointKeyFrame KeyTime="00:00:01.2500000" 
                                Value="0.834,0.127"/>
                             <SplinePointKeyFrame KeyTime="00:00:01.5" 
                                Value="0.999,0.464"/>
                             <SplinePointKeyFrame KeyTime="00:00:01.7500000" 
                                Value="0.916,0.778"/>
                             <SplinePointKeyFrame KeyTime="00:00:02" 
                                Value="0.538,0.999"/>
                        </PointAnimationUsingKeyFrames>
                        <DoubleAnimationUsingKeyFrames BeginTime="00:00:00" 
                                 Storyboard.TargetName="ellipse"
                                 Storyboard.TargetProperty="Opacity">
                             <SplineDoubleKeyFrame KeyTime="00:00:00" 
                                 Value="1"/>
                        </DoubleAnimationUsingKeyFrames>
                    </Storyboard>
               </vsm:VisualState>
           <vsm:VisualState x:Name="IdleState"/>
       </vsm:VisualStateGroup>
   </vsm:VisualStateManager.VisualStateGroups>

步骤 2.3:为我们的控件编写代码

我们刚刚创建了控件的文件结构和控件模板。现在我们应该为其添加代码。在控件模板中,我们将 EllipseStrokeThickness 绑定到一个名为 StrokeThickness 的属性。因此,我们首先将 StrokeThickness 添加到我们的控件代码中。

#region StrokeThickness Property
/// <summary>
/// Gets or sets the width of the <see cref="WaitingIcon"/> stroke outline. 
/// </summary>
/// <value>The width of the <see cref="WaitingIcon"/>  outline, in pixels. 
/// The default value is 0. </value>
public double StrokeThickness
{
    get { return (double)GetValue(StrokeThicknessProperty); }
    set { SetValue(StrokeThicknessProperty, value); }
}

/// <summary>
/// Identifies the <see cref="StrokeThickness"/> dependency property. 
/// </summary>
public static readonly DependencyProperty StrokeThicknessProperty =
      DependencyProperty.Register("StrokeThickness", typeof(double), 
        typeof(WaitingIcon), new PropertyMetadata(6.0)); 
#endregion

创建此控件的目的是能够指示应用程序正在忙碌地执行某些操作,因此我们的控件应该有一个属性来指示它是否处于忙碌状态。

#region IsBusy Property
/// <summary>
/// Gets or sets a value indicating is busy or not
/// </summary>
/// <value>A value indicating whether the control is in busy state or not.
/// <para>The default value is <c>false</c>.</para></value>
public bool IsBusy
{
    get { return (bool)GetValue(IsBusyProperty); }
    set { SetValue(IsBusyProperty, value); }
}

/// <summary>
/// Identifies the <see cref="IsBusy"/> dependency property. 
/// </summary>
public static readonly DependencyProperty IsBusyProperty =
       DependencyProperty.Register("IsBusy", typeof(bool), 
          typeof(WaitingIcon),
            new PropertyMetadata(false, IsBusyPropertyChanged));

/// <summary>
/// The <see cref="IsBusy"/> property changed callback function.
/// </summary>
/// <param name="d">The <see cref="WaitingIcon"/>
/// control whosevsee cref="IsBusy"/> property changed.</param>
/// <param name="e">The DependencyPropertyChangedEventArgs
///     contains old and new value.</param>
private static void IsBusyPropertyChanged(DependencyObject d, 
                    DependencyPropertyChangedEventArgs e)
{
     WaitingIcon wi = d as WaitingIcon;
     wi.IsBusyChanged((bool)e.OldValue, (bool)e.NewValue);
} 
#endregion

IsBusy 属性设置为 true 时,我们的控件应该可见并显示我们创建的动画。我们只需转到“BusyState”。

/// <summary>
/// The <see cref="IsBusy "/> property changed.
/// </summary>
/// <param name="oldValue">The old value of the
///    <see cref="IsBusy"/> property.</param>
/// <param name="newValue">The new  value of the
///   <see cref="IsBusy"/> property.</param>
protected virtual void IsBusyChanged(bool oldValue, bool newValue)
{
    if (newValue)
    {
         VisualStateManager.GoToState(this, WaitingIcon.BusyStateName, false);
    }
    else
    {
         VisualStateManager.GoToState(this, WaitingIcon.IdleStateName, false);
    }
}

最后一步是将默认控件模板应用于我们的控件。在构造函数中,将 DefaultStyleKey 设置为我们控件的类型。

/// <summary>
/// Initialize a new instance of <see cref="WaitingIcon"/> class.
/// </summary>
public WaitingIcon()
{
    // The default style key
    this.DefaultStyleKey = typeof(WaitingIcon);
}

/// <summary>
/// Apply new template
/// </summary>
public override void OnApplyTemplate()
{
    base.OnApplyTemplate();
    
    if (this.IsBusy)
    {
         // if set to busy in XAML, we must go to BusyState here
         VisualStateManager.GoToState(this, WaitingIcon.BusyStateName, false);
    }
}

编译我们刚刚创建的项目,我们的控件就可以使用了。

第三部分:使用控件

创建一个新的 Silverlight 应用程序,并为我们的控件程序集添加引用。然后,在 Page.xaml 文件中,将您的控件放在您想要的位置,并设置背景和描边厚度,或者直接使用默认设置。

<UserControl x:Class="WaitingTest.Page"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 
    xmlns:cdc="clr-namespace:Cokkiy.Display;assembly=Cokkiy.Display.WaitingIcon"
    Width="400" Height="300">
    <Grid x:Name="LayoutRoot" Background="#FF090808">
        <cdc:WaitingIcon Width="20" Height="20" IsBusy="True">
            <cdc:WaitingIcon.Background>
                <LinearGradientBrush EndPoint="0.5,1" StartPoint="0.5,0">
                    <GradientStop Color="#FF070B9C" Offset="0.57599997520446777"/>
                    <GradientStop Color="#FFFFFFFF" Offset="1"/>
                </LinearGradientBrush>
            </cdc:WaitingIcon.Background>
        </cdc:WaitingIcon>
   </Grid>

当您的应用程序处于忙碌状态时,将在代码中设置 IsBusy 属性。

关注点

您可能会注意到,在 IsBusyChangedOnApplyTemplate 函数中,我都执行了相同的检查:检查 IsBusy 属性的值,并在设置为 true 时转到“BusyState”。原因是当您在 XAML 中将 IsBusy 设置为 true 时,IsBusyChanged 函数在模板应用之前被调用。此时,“BusyState”VisualState 根本不存在,什么都不会发生。因此,您需要重新检查,当模板应用后,如果值为 true,您应该在此处转到“BusyState”。

© . All rights reserved.