пятница, 27 августа 2010 г.

Silvelight 4 + Ria Services – Часть 2

Теперь чуть более подробно рассмотрим созданный по умолчанию Silverlight проект.

clip_image002

Папка Controls содержит несколько полезных контролов, расширяющих функциональность существующих.

BusyIndicator – сообщает пользователю о том, что текущая операция еще не завершена и ему следует подождать. Все операции по работе с сервисами в Silverlight являются асинхронными, так что такой контрол, безусловно, является крайне полезным.

namespace BusinessApplication1.Controls

{

using System;

using System.Windows;

using System.Windows.Controls;

using System.Windows.Shapes;

using System.Windows.Threading;

/// <summary>

/// A control to provide a visual indicator when an application is busy.

/// </summary>

[TemplateVisualState(Name = VisualStates.StateIdle, GroupName = VisualStates.GroupBusyStatus)]

[TemplateVisualState(Name = VisualStates.StateBusy, GroupName = VisualStates.GroupBusyStatus)]

[TemplateVisualState(Name = VisualStates.StateVisible, GroupName = VisualStates.GroupVisibility)]

[TemplateVisualState(Name = VisualStates.StateHidden, GroupName = VisualStates.GroupVisibility)]

[StyleTypedProperty(Property = "OverlayStyle", StyleTargetType = typeof(Rectangle))]

[StyleTypedProperty(Property = "ProgressBarStyle", StyleTargetType = typeof(ProgressBar))]

public class BusyIndicator : ContentControl

{

/// <summary>

/// Gets or sets a value indicating whether the BusyContent is visible.

/// </summary>

protected bool IsContentVisible { get; set; }

/// <summary>

/// Timer used to delay the initial display and avoid flickering.

/// </summary>

private DispatcherTimer _displayAfterTimer;

/// <summary>

/// Instantiates a new instance of the BusyIndicator control.

/// </summary>

public BusyIndicator()

{

DefaultStyleKey = typeof(BusyIndicator);

_displayAfterTimer = new DispatcherTimer();

_displayAfterTimer.Tick += new EventHandler(DisplayAfterTimerElapsed);

}

/// <summary>

/// Overrides the OnApplyTemplate method.

/// </summary>

public override void OnApplyTemplate()

{

base.OnApplyTemplate();

ChangeVisualState(false);

}

/// <summary>

/// Handler for the DisplayAfterTimer.

/// </summary>

/// <param name="sender">Event sender.</param>

/// <param name="e">Event arguments.</param>

private void DisplayAfterTimerElapsed(object sender, EventArgs e)

{

_displayAfterTimer.Stop();

IsContentVisible = true;

ChangeVisualState(true);

}

/// <summary>

/// Changes the control's visual state(s).

/// </summary>

/// <param name="useTransitions">True if state transitions should be used.</param>

protected virtual void ChangeVisualState(bool useTransitions)

{

VisualStateManager.GoToState(this, IsBusy ? VisualStates.StateBusy : VisualStates.StateIdle, useTransitions);

VisualStateManager.GoToState(this, IsContentVisible ? VisualStates.StateVisible : VisualStates.StateHidden, useTransitions);

}

/// <summary>

/// Gets or sets a value indicating whether the busy indicator should show.

/// </summary>

public bool IsBusy

{

get { return (bool)GetValue(IsBusyProperty); }

set { SetValue(IsBusyProperty, value); }

}

/// <summary>

/// Identifies the IsBusy dependency property.

/// </summary>

public static readonly DependencyProperty IsBusyProperty = DependencyProperty.Register(

"IsBusy",

typeof(bool),

typeof(BusyIndicator),

new PropertyMetadata(false, new PropertyChangedCallback(OnIsBusyChanged)));

/// <summary>

/// IsBusyProperty property changed handler.

/// </summary>

/// <param name="d">BusyIndicator that changed its IsBusy.</param>

/// <param name="e">Event arguments.</param>

private static void OnIsBusyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)

{

((BusyIndicator)d).OnIsBusyChanged(e);

}

/// <summary>

/// IsBusyProperty property changed handler.

/// </summary>

/// <param name="e">Event arguments.</param>

protected virtual void OnIsBusyChanged(DependencyPropertyChangedEventArgs e)

{

if (IsBusy)

{

if (DisplayAfter.Equals(TimeSpan.Zero))

{

// Go visible now

IsContentVisible = true;

}

else

{

// Set a timer to go visible

_displayAfterTimer.Interval = DisplayAfter;

_displayAfterTimer.Start();

}

}

else

{

// No longer visible

_displayAfterTimer.Stop();

IsContentVisible = false;

}

ChangeVisualState(true);

}

/// <summary>

/// Gets or sets a value indicating the busy content to display to the user.

/// </summary>

public object BusyContent

{

get { return (object)GetValue(BusyContentProperty); }

set { SetValue(BusyContentProperty, value); }

}

/// <summary>

/// Identifies the BusyContent dependency property.

/// </summary>

public static readonly DependencyProperty BusyContentProperty = DependencyProperty.Register(

"BusyContent",

typeof(object),

typeof(BusyIndicator),

new PropertyMetadata(null));

/// <summary>

/// Gets or sets a value indicating the template to use for displaying the busy content to the user.

/// </summary>

public DataTemplate BusyContentTemplate

{

get { return (DataTemplate)GetValue(BusyContentTemplateProperty); }

set { SetValue(BusyContentTemplateProperty, value); }

}

/// <summary>

/// Identifies the BusyTemplate dependency property.

/// </summary>

public static readonly DependencyProperty BusyContentTemplateProperty = DependencyProperty.Register(

"BusyContentTemplate",

typeof(DataTemplate),

typeof(BusyIndicator),

new PropertyMetadata(null));

/// <summary>

/// Gets or sets a value indicating how long to delay before displaying the busy content.

/// </summary>

public TimeSpan DisplayAfter

{

get { return (TimeSpan)GetValue(DisplayAfterProperty); }

set { SetValue(DisplayAfterProperty, value); }

}

/// <summary>

/// Identifies the DisplayAfter dependency property.

/// </summary>

public static readonly DependencyProperty DisplayAfterProperty = DependencyProperty.Register(

"DisplayAfter",

typeof(TimeSpan),

typeof(BusyIndicator),

new PropertyMetadata(TimeSpan.FromSeconds(0.1)));

/// <summary>

/// Gets or sets a value indicating the style to use for the overlay.

/// </summary>

public Style OverlayStyle

{

get { return (Style)GetValue(OverlayStyleProperty); }

set { SetValue(OverlayStyleProperty, value); }

}

/// <summary>

/// Identifies the OverlayStyle dependency property.

/// </summary>

public static readonly DependencyProperty OverlayStyleProperty = DependencyProperty.Register(

"OverlayStyle",

typeof(Style),

typeof(BusyIndicator),

new PropertyMetadata(null));

/// <summary>

/// Gets or sets a value indicating the style to use for the progress bar.

/// </summary>

public Style ProgressBarStyle

{

get { return (Style)GetValue(ProgressBarStyleProperty); }

set { SetValue(ProgressBarStyleProperty, value); }

}

/// <summary>

/// Identifies the ProgressBarStyle dependency property.

/// </summary>

public static readonly DependencyProperty ProgressBarStyleProperty = DependencyProperty.Register(

"ProgressBarStyle",

typeof(Style),

typeof(BusyIndicator),

new PropertyMetadata(null));

}

}

Xaml файл:

<ResourceDictionary

xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"

xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"

xmlns:sys="clr-namespace:System;assembly=mscorlib"

xmlns:appControls="clr-namespace:BusinessApplication1.Controls">

<Style TargetType="appControls:BusyIndicator" x:Key="busyIndicatorDefaultStyle">

<Setter Property="BusyContent" Value="Please wait..."/>

<Setter Property="IsTabStop" Value="False"/>

<Setter Property="OverlayStyle">

<Setter.Value>

<Style TargetType="Rectangle">

<Setter Property="Fill" Value="White"/>

<Setter Property="Opacity" Value="0.5"/>

</Style>

</Setter.Value>

</Setter>

<Setter Property="ProgressBarStyle">

<Setter.Value>

<Style TargetType="ProgressBar">

<Setter Property="IsIndeterminate" Value="True"/>

<Setter Property="Height" Value="15"/>

<Setter Property="Margin" Value="8,0,8,8"/>

</Style>

</Setter.Value>

</Setter>

<Setter Property="DisplayAfter" Value="00:00:00.1"/>

<Setter Property="HorizontalAlignment" Value="Stretch"/>

<Setter Property="VerticalAlignment" Value="Stretch"/>

<Setter Property="HorizontalContentAlignment" Value="Stretch"/>

<Setter Property="VerticalContentAlignment" Value="Stretch"/>

<Setter Property="Template">

<Setter.Value>

<ControlTemplate TargetType="appControls:BusyIndicator">

<Grid>

<VisualStateManager.VisualStateGroups>

<VisualStateGroup x:Name="VisibilityStates">

<VisualState x:Name="Hidden">

<Storyboard>

<ObjectAnimationUsingKeyFrames BeginTime="00:00:00" Duration="00:00:00.001" Storyboard.TargetName="busycontent" Storyboard.TargetProperty="(UIElement.Visibility)">

<DiscreteObjectKeyFrame KeyTime="00:00:00">

<DiscreteObjectKeyFrame.Value>

<Visibility>Collapsed</Visibility>

</DiscreteObjectKeyFrame.Value>

</DiscreteObjectKeyFrame>

</ObjectAnimationUsingKeyFrames>

<ObjectAnimationUsingKeyFrames BeginTime="00:00:00" Duration="00:00:00.001" Storyboard.TargetName="overlay" Storyboard.TargetProperty="(UIElement.Visibility)">

<DiscreteObjectKeyFrame KeyTime="00:00:00">

<DiscreteObjectKeyFrame.Value>

<Visibility>Collapsed</Visibility>

</DiscreteObjectKeyFrame.Value>

</DiscreteObjectKeyFrame>

</ObjectAnimationUsingKeyFrames>

</Storyboard>

</VisualState>

<VisualState x:Name="Visible">

<Storyboard>

<ObjectAnimationUsingKeyFrames BeginTime="00:00:00" Duration="00:00:00.001" Storyboard.TargetName="busycontent" Storyboard.TargetProperty="(UIElement.Visibility)">

<DiscreteObjectKeyFrame KeyTime="00:00:00">

<DiscreteObjectKeyFrame.Value>

<Visibility>Visible</Visibility>

</DiscreteObjectKeyFrame.Value>

</DiscreteObjectKeyFrame>

</ObjectAnimationUsingKeyFrames>

<ObjectAnimationUsingKeyFrames BeginTime="00:00:00" Duration="00:00:00.001" Storyboard.TargetName="overlay" Storyboard.TargetProperty="(UIElement.Visibility)">

<DiscreteObjectKeyFrame KeyTime="00:00:00">

<DiscreteObjectKeyFrame.Value>

<Visibility>Visible</Visibility>

</DiscreteObjectKeyFrame.Value>

</DiscreteObjectKeyFrame>

</ObjectAnimationUsingKeyFrames>

</Storyboard>

</VisualState>

</VisualStateGroup>

<VisualStateGroup x:Name="BusyStatusStates">

<VisualState x:Name="Idle">

<Storyboard>

<ObjectAnimationUsingKeyFrames BeginTime="00:00:00" Duration="00:00:00.001" Storyboard.TargetName="content" Storyboard.TargetProperty="(Control.IsEnabled)">

<DiscreteObjectKeyFrame KeyTime="00:00:00">

<DiscreteObjectKeyFrame.Value>

<sys:Boolean>True</sys:Boolean>

</DiscreteObjectKeyFrame.Value>

</DiscreteObjectKeyFrame>

</ObjectAnimationUsingKeyFrames>

</Storyboard>

</VisualState>

<VisualState x:Name="Busy">

<Storyboard>

<ObjectAnimationUsingKeyFrames BeginTime="00:00:00" Duration="00:00:00.001" Storyboard.TargetName="content" Storyboard.TargetProperty="(Control.IsEnabled)">

<DiscreteObjectKeyFrame KeyTime="00:00:00">

<DiscreteObjectKeyFrame.Value>

<sys:Boolean>False</sys:Boolean>

</DiscreteObjectKeyFrame.Value>

</DiscreteObjectKeyFrame>

</ObjectAnimationUsingKeyFrames>

</Storyboard>

</VisualState>

</VisualStateGroup>

</VisualStateManager.VisualStateGroups>

<ContentControl

x:Name="content"

Content="{TemplateBinding Content}"

ContentTemplate="{TemplateBinding ContentTemplate}"

HorizontalContentAlignment="{TemplateBinding HorizontalContentAlignment}"

VerticalContentAlignment="{TemplateBinding VerticalContentAlignment}"/>

<Rectangle

x:Name="overlay"

Style="{TemplateBinding OverlayStyle}"/>

<ContentPresenter

x:Name="busycontent">

<Grid

HorizontalAlignment="Center"

VerticalAlignment="Center">

<Border

Background="White"

BorderThickness="1"

CornerRadius="2">

<Border.BorderBrush>

<LinearGradientBrush StartPoint="0.5,0" EndPoint="0.5,1">

<GradientStop Color="#FFA3AEB9" Offset="0"/>

<GradientStop Color="#FF8399A9" Offset="0.375"/>

<GradientStop Color="#FF718597" Offset="0.375"/>

<GradientStop Color="#FF617584" Offset="1"/>

</LinearGradientBrush>

</Border.BorderBrush>

<Border

CornerRadius="1.5"

Margin="1">

<Border.Background>

<LinearGradientBrush StartPoint="0.5,0" EndPoint="0.5,1">

<GradientStop Color="#FFF6F8F9" Offset="0.02"/>

<GradientStop Color="#FFB8B8B8" Offset="0.996"/>

</LinearGradientBrush>

</Border.Background>

<Grid MinWidth="150">

<Grid.RowDefinitions>

<RowDefinition/>

<RowDefinition Height="Auto"/>

</Grid.RowDefinitions>

<ContentPresenter

Content="{TemplateBinding BusyContent}"

ContentTemplate="{TemplateBinding BusyContentTemplate}"

Margin="8"/>

<ProgressBar

Grid.Row="1"

Style="{TemplateBinding ProgressBarStyle}"/>

</Grid>

</Border>

</Border>

</Grid>

</ContentPresenter>

</Grid>

</ControlTemplate>

</Setter.Value>

</Setter>

</Style>

</ResourceDictionary>

CustomDataForm расширяет функциональность стандартного контрола DataForm так, чтобы поле с паролем заменялось на поле со специальным текстовым полем для пароля (тот что со звездочками). Вообще, контрол не слишком полезен, так как не часто встречается пароль в формах с данными. С другой стороны, на примере этого контрола можно разобраться со многими аспектами работы с DataForm (а это очень важно для Silverlight Business Application).

namespace BusinessApplication1.Controls

{

using System;

using System.Collections.Generic;

using System.Reflection;

using System.Windows;

using System.Windows.Controls;

using System.Windows.Input;

using BusinessApplication1.Web;

/// <summary>

/// Enhances <see cref="DataForm" /> functionality by using a <see cref="PasswordBox" />

/// control for password fields and exposing a <see cref="CustomDataForm.Fields"/> collection

/// to allow runtime access to <see cref="DataForm" /> fields.

/// </summary>

public class CustomDataForm : DataForm

{

private Dictionary<string, DataField> fields = new Dictionary<string, DataField>();

/// <summary>

/// Returns a <c>Dictionary&lt;string,DataField&gt;</c> containing all the

/// <see cref="DataField" /> controls being displayed keyed by the

/// property name to which each field is bound.

/// </summary>

public Dictionary<string, DataField> Fields

{

get { return this.fields; }

}

/// <summary>

/// Extends <see cref="DataForm.OnAutoGeneratingField" /> by replacing <see cref="TextBox"/>es with <see cref="PasswordBox"/>es

/// whenever applicable

/// </summary>

protected override void OnAutoGeneratingField(DataFormAutoGeneratingFieldEventArgs e)

{

if (e == null)

{

throw new ArgumentNullException("e");

}

// Get metadata about the property being defined

PropertyInfo propertyInfo = this.CurrentItem.GetType().GetProperty(e.PropertyName);

// Do the password field replacement if that is the case

if (e.Field.Content is TextBox && this.IsPasswordProperty(propertyInfo))

{

e.Field.ReplaceTextBox(new PasswordBox(), PasswordBox.PasswordProperty);

}

// Keep this newly generated field accessible through the Fields property

this.fields[e.PropertyName] = e.Field;

// Call base implementation (which will call other event listeners)

base.OnAutoGeneratingField(e);

}

/// <param name="propertyInfo">The entity property being analyzed</param>

/// <summary>

/// Returns whether the given property should be represented by a <see cref="PasswordBox" /> or not.

/// The default implementation will simply use a naming convention and returns true if the

/// property contains the word "Password".

/// </summary>

protected virtual bool IsPasswordProperty(PropertyInfo propertyInfo)

{

if (propertyInfo == null)

{

throw new ArgumentNullException("propertyInfo");

}

// Suggestion: to handle more complex scenarios, allow an entity to override

// this mechanism by using the System.ComponentModel.DataAnnotations.UIHintAttribute

return propertyInfo.Name.IndexOf("Password", StringComparison.OrdinalIgnoreCase) != -1;

}

}

}

Пока код может быть непонятным, но позже мы с ним разбиремся.

Папка Helpers содержит несколько интересных конвертеров, хотя, пожалуй, они не слишком полезны. Несколько простых, но очень полезных конвертеров мы разработаем позже.

Папка Views содержит всю визуальную часть приложения, то есть все его страницы, окна и т.д.

Комментариев нет:

Отправить комментарий