試用Workflow開發WPF應用程序
研究了半個月的《WF高級程序設計》,我覺得這個框架做的太有價值了,又將WCF和Web服務結合起來了,提高了它的應用領域。工作流使我們能夠輕松地建模系統,實現真正邏輯意義上的人機交互功能。這在游戲開發中特別有用,而且將開發人員從架構的角度來設計程序,提高程序設計的邏輯性和可讀性。由于書上的例子都是在WinForm和控制臺上的,所以我覺得有必要運用到WPF開發中。由于WPF架構與WinForm許多的差異,所以我試著做了一個WPF的隨機選數游戲來體驗一下WPF與WF的結合。
由于本例子主要目的在于體現一個順序機制,按理說應該是用順序工作流來處理這個機制,考慮到以后的擴展,我選擇了狀態機工作流。我們先來了解一下狀態機工作流,所謂“狀態機工作流”是指一組應用程序狀態以及狀態之間的可能的轉換。每個狀態都可以處理多個外部事件,外部事件能夠觸發器子活動的執行,而自活動又可能包含一個到其他狀態的轉換。
先來看一下工作流機制:
一、建立一個空的工作流項目[一個dll]
1、定義一個公共的接口
在這個接口中設計了五個事件和一個方法。定義的事件是為了提供給工作流的使用。
Generic.xaml
2、實現接口服務的PointSelectService類,這個外部服務類提供一組事件調用方法和一個事件,供宿主使用,相應的代碼如下:
代碼
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Workflow.Activities;
using System.Workflow.Runtime;
namespace SelectGameWorkflow
{
/// <summary>
/// 實現服務接口類
/// </summary>
public class PointSelectService:IPointSelect
{
#region IPointSelect 成員
public event EventHandler<System.Workflow.Activities.ExternalDataEventArgs> StartSelect;
public event EventHandler<System.Workflow.Activities.ExternalDataEventArgs> StopSelect;
public event EventHandler<System.Workflow.Activities.ExternalDataEventArgs> First;
public event EventHandler<System.Workflow.Activities.ExternalDataEventArgs> Second;
public event EventHandler<System.Workflow.Activities.ExternalDataEventArgs> Third;
public event EventHandler<ExternalDataEventArgs> Completed;
/// <summary>
/// 調用宿主的事件
/// </summary>
/// <param name="message"></param>
public void SendMessage(string message)
{
if (MessageReceived != null)
MessageReceived(this, new MessageReceivedEventArgs(WorkflowEnvironment.WorkflowInstanceId, message));
}
#endregion
/// <summary>
///
/// 工作流使用的事件
/// </summary>
public event EventHandler<MessageReceivedEventArgs> MessageReceived;
#region 以下定義了一組供宿主使用的方法
public void OnStartSelect(ExternalDataEventArgs e)
{
if (StartSelect != null)
StartSelect(null, e);
}
public void OnStopSelect(ExternalDataEventArgs e)
{
if (StopSelect != null)
StopSelect(null, e);
}
public void OnFirst(ExternalDataEventArgs e)
{
if (First != null)
First(null, e);
}
public void OnSecond(ExternalDataEventArgs e)
{
if (Second != null)
Second(null, e);
}
public void OnThird(ExternalDataEventArgs e)
{
if (Third != null)
Third(null, e);
}
public void OnCompleted(ExternalDataEventArgs e)
{
if (Completed != null)
Completed(null, e);
}
#endregion
}
這里還定義了一個EventArgs的派生類來傳遞服務消息:
代碼
/// <summary>
/// 把工作流的相應消息發送到本地服務中
/// </summary>
[Serializable]
public class MessageReceivedEventArgs : ExternalDataEventArgs
{
public string Message { get; set; }
/// <summary>
/// 在構造器中傳遞消息,其中會將工作流的ID傳遞到基類中
/// </summary>
/// <param name="instanceId"></param>
/// <param name="message"></param>
public MessageReceivedEventArgs(Guid instanceId, string message):base(instanceId )
{
this.Message = message;
}
}
3、在剛才的項目中添加一個狀態機工作流:
這里面定義了一組狀態事件處理,如表所示:
除了DoneState狀態之外,每個State都有定義了三個活動,按順序分別是:
handleExternalEventActivity、callExternalMethodActivity、setStateActivity,可以分別設置他們的接口、事件和方法,如圖所示:
現在我們可以重新生成動態庫了,得到SelectGameWorkflow.dll文件
二、添加一個WPF項目作為工作流的宿主,這個WPF命名為SelectGameWPF,
1、 先設計界面,效果如下:
相應的XAML代碼如下:
代碼
<Window x:Class="SelectGameWPF.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="碰手氣" Height="300" Width="300" xmlns:my="clr-namespace:C4F.VistaP2P.WPF.Chat;assembly=C4F_P2PWPFControls" Loaded="Window_Loaded">
<Grid Background="LightBlue" >
<Grid.RowDefinitions>
<RowDefinition Height="50"/>
<RowDefinition/>
</Grid.RowDefinitions>
<TextBlock Height="58" Text="{Binding Number}" x:Name="numberText" VerticalAlignment="Top" FontSize="48" HorizontalAlignment="Center" FontFamily="Broadway">
<TextBlock.OpacityMask>
<LinearGradientBrush EndPoint="0.5,1" StartPoint="0.5,0">
<GradientStop Color="#E5000000" Offset="0"/>
<GradientStop Color="#99FFFFFF" Offset="1"/>
</LinearGradientBrush>
</TextBlock.OpacityMask>
<TextBlock.Foreground>
<LinearGradientBrush EndPoint="0.5,1" StartPoint="0.5,0">
<GradientStop Color="#FFED7B69" Offset="0"/>
<GradientStop Color="#CCFFFFFF" Offset="0.385"/>
<GradientStop Color="#E8E78851" Offset="0.931"/>
</LinearGradientBrush>
</TextBlock.Foreground>
</TextBlock>
<GroupBox HorizontalAlignment="Left" VerticalAlignment="Top" Grid.Row="1">
<GroupBox.Header>
<TextBlock Text="控制"/>
</GroupBox.Header>
<StackPanel Width="100" x:Name="controlButtons">
<Button x:Name="startBtn" Content="開 始" Height="40" Margin="5,20" Click="startBtn_Click"/>
<Button x:Name="stopBtn" Content="停 止" Height="40" Margin="5,0,5,20" Click="stopBtn_Click"/>
</StackPanel>
</GroupBox>
<GroupBox x:Name="control" Header="玩家" HorizontalAlignment="Right" VerticalAlignment="Top" Grid.Row="1">
<StackPanel Width="100" x:Name="playerButtons">
<Button x:Name="firstBtn" Content="第一個玩家" Height="40" Margin="5,20" Click="firstBtn_Click"/>
<Button x:Name="secondBtn" Content="第二個玩家" Height="40" Margin="5,0,5,20" Click="secondBtn_Click"/>
<Button x:Name="thirdBtn" Content="第三個玩家" Height="40" Margin="5,0,5,20" Click="thirdBtn_Click"/>
</StackPanel>
</GroupBox>
<TextBlock Grid.Row="1" Height="23" HorizontalAlignment="Left" Name="textBlock1" Text="{Binding Message}" VerticalAlignment="Bottom" Width="160" />
</Grid>
</Window>
2、添加兩個更新界面的依賴屬性:
代碼
#region 定義一個隨機數和消息
public static readonly DependencyProperty NumberProperty = DependencyProperty.Register("Number", typeof(int), typeof(Window1), new PropertyMetadata(0));
public static readonly DependencyProperty MessageProperty = DependencyProperty.Register("Message", typeof(string), typeof(Window1), new PropertyMetadata(string.Empty));
public int Number {
get { return (int)GetValue(NumberProperty); }
set { SetValue(NumberProperty, value); }
}
public string Message {
get { return (string)GetValue(MessageProperty); }
set { SetValue(MessageProperty, value); }
}
#endregion
為了能夠在UI線程中更新視覺元素,我們使用了和WinForm的Invoke異步調用的相同的Dispatcher類的BeginInvoke方法:
如下所示:
代碼
private delegate void UpdateDelegate();
/// <summary>
/// 在指定的時間段內產生隨機數
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
void timer_Tick(object sender, EventArgs e)
{
///將數字呈現到前臺
UpdateDelegate theDelg = () =>
{
Number = rand.Next(100);
};
Dispatcher.BeginInvoke(theDelg, null);
}
其他的更新方式如上面所示
3、接下來調用工作流項目以及將上面的SelectGameService服務加入到運行時:
代碼
/// <summary>
/// 初始化工作流服務
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void Window_Loaded(object sender, RoutedEventArgs e)
{
manager = new WorkflowRuntimeManager(new WorkflowRuntime());
//在轉換到新的狀態時發生
manager.WorkflowRuntime.WorkflowIdled += new EventHandler<WorkflowEventArgs>(WorkflowRuntime_WorkflowIdled);
AddServices(manager.WorkflowRuntime);
}
/// <summary>
/// 處理Idle事件
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
void WorkflowRuntime_WorkflowIdled(object sender, WorkflowEventArgs e)
{
UpdateDelegate theDelg = () =>
{
EnableEventButton(false);
//得到內部隊列信息
ReadOnlyCollection<WorkflowQueueInfo> queueInfoData = instanceWrapper.WorkflowInstance.GetWorkflowQueueData();
if (queueInfoData != null) {
foreach (WorkflowQueueInfo info in queueInfoData) {
EventQueueName eventQueue = info.QueueName as EventQueueName;
if (eventQueue == null)
break;
EnableButtonForEvent(eventQueue.MethodName);
}
}
};
Dispatcher.BeginInvoke(theDelg, null);
}
/// <summary>
/// 根據事件名啟用按鈕
/// </summary>
/// <param name="p"></param>
private void EnableButtonForEvent(string eventName)
{
var buttons1 = controlButtons.Children;
var buttons2 = playerButtons.Children;
foreach (Button btn in buttons1) {
if (btn.Tag.ToString() == eventName)
btn.IsEnabled = true;
}
foreach (Button btn in buttons2)
{
if (btn.Tag.ToString() == eventName)
btn.IsEnabled = true;
}
}
/// <summary>
/// 將服務添加到工作流運行時
/// </summary>
/// <param name="workflowRuntime"></param>
private void AddServices(WorkflowRuntime workflowRuntime)
{
ExternalDataExchangeService exchangeService = new ExternalDataExchangeService();
workflowRuntime.AddService(exchangeService);
pointSelectService = new PointSelectService();
pointSelectService.MessageReceived += new EventHandler<MessageReceivedEventArgs>(pointSelectService_MessageReceived);
exchangeService.AddService(pointSelectService);
}
void pointSelectService_MessageReceived(object sender, MessageReceivedEventArgs e)
{
UpdateDelegate theDelg = () =>
{
Message = e.Message;
};
//保存工作流實例標識
System.Threading.Thread.Sleep(1000);
timer.Start();
instanceId = e.InstanceId;
Dispatcher.BeginInvoke(theDelg, null);
}
}
4、初始化按鈕狀態和定時器
代碼
public Window1()
{
InitializeComponent();
timer = new DispatcherTimer();
timer.Interval = new TimeSpan(0, 0, 0, 0, 300);
timer.Tick += new EventHandler(timer_Tick);
rand = new Random();
//設置按鈕標識
startBtn.Tag = "StartSelect";
stopBtn.Tag = "StopSelect";
firstBtn.Tag = "First";
secondBtn.Tag = "Second";
thirdBtn.Tag = "Third";
EnableEventButton(false);
DataContext = this;
}
/// <summary>
/// 控制按鈕的狀態
/// </summary>
/// <param name="p"></param>
private void EnableEventButton(bool p)
{
stopBtn.IsEnabled = p;
firstBtn.IsEnabled = p;
secondBtn.IsEnabled = p;
thirdBtn.IsEnabled = p;
}
5、添加所有按鈕的Click事件,其目的是為了調用服務的方法和宿主本身的定時器的,如下所示:
代碼
#region Click events
private void firstBtn_Click(object sender, System.Windows.RoutedEventArgs e)
{
// TODO: Add event handler implementation here.
try
{
pointSelectService.OnFirst(GetEventArgs());
timer.Stop();
if (Number > temp)
{
temp = Number;
tempPlayer = "First";
}
}
catch(Exception ex) {
HandleException(ex);
}
}
private void secondBtn_Click(object sender, System.Windows.RoutedEventArgs e)
{
// TODO: Add event handler implementation here.
try
{
pointSelectService.OnSecond(GetEventArgs());
timer.Stop();
if (Number > temp)
{
temp = Number;
tempPlayer = "Second";
}
}
catch (Exception ex) {
HandleException(ex);
}
}
private void thirdBtn_Click(object sender, System.Windows.RoutedEventArgs e)
{
// TODO: Add event handler implementation here.
try
{
pointSelectService.OnThird(GetEventArgs());
timer.Stop();
if (Number > temp)
{
temp = Number;
tempPlayer = "Third";
}
if (tempPlayer != string.Empty)
{
UpdateDelegate theDelg = () =>
{
Message = tempPlayer + "得到最大數: " + temp.ToString();
};
Dispatcher.BeginInvoke(theDelg, null);
}
}
catch (Exception ex) {
HandleException(ex);
}
}
#endregion
至此,整個工程全部完成,現在在啟動應用程序以后,我們就可以按如開頭的工作流圖的順序依次 :Start-First-Second-Third-Start。
其實這個程序我們可以演化到多用戶順序執行程序,如網絡麻將、斗地主等互動游戲的結合。
完整代碼下載:Workflow


浙公網安備 33010602011771號