書名:WPF專業(yè)編程指南
作者:李應(yīng)保
出版社:電子工業(yè)出版社
出版時間:2010-01
ISBN:9787121100116
一、附加屬性實例
- 這個例子中使用附加屬性來實現(xiàn)編輯軟件中常用的“重做/取消”功能。
WPF的Text Box及相關(guān)元素都內(nèi)在支持“重做/取消”和“拷貝/復(fù)制”這樣一些常用的編輯功能,但WPF的“重做/取消”功能只對具有當(dāng)前輸入焦點的TextBox有效。
在這里要做的是:“取消和重做”可以對多個TextBox同時進(jìn)行。
思路:
- 為此,要把用戶的操作用堆棧管理起來,當(dāng)用戶輸入的時候,把用戶的操作放入堆棧,當(dāng)用戶要取消一個輸入的時候可把該輸入放到另外一堆棧;
在用戶要進(jìn)行重做時,只要把用戶的輸入從另外一個堆棧中取出來就可以了。
由于堆棧的先入先出功能,可以很方便地管理用戶“最近”的 操作。
設(shè)計
- 首先我們定義一個UndoOperation操作類:
public class UndoOperation
{
public TextBoxBase Sender;
public UndoAction Action;
public UndoOperation(TextBoxBase sender, UndoAction action)
{
this.Sender = sender;
this.Action = action;
}
}
這個類中有兩個域,一個是Sender表示當(dāng)前操作哪一個UI元素,其類型為TextBoxBase;
另一個是UndoAction。
WPF中TextBoxBase是TextBox及RichTextBox兩個類的基類。
WPF中定義了一個UndoAction枚舉類型,可取Create、Clear、none、Undo,Redo、Merge這樣一些值。UndoService是主要完成“取消/重做”操作的類,其中含有兩個管理“取消/重做”操作的堆棧:
Stack<UndoOperation> undoStack = new Stack<UndoOperation>();
Stack<UndoOperation> redoStack = new Stack<UndoOperation>();
- 如前所述,附加屬性是相關(guān)屬性的一種。和相關(guān)屬性一樣,需要定義一個public static類型為DependencyProperty的變量:
public static readonly DependencyProperty SharedUndoRedoScopeProperty =
DependencyProperty.RegisterAttached("SharedUndoRedoScope",
typeof(UndoService),
typeof(UndoService),
new FrameworkPropertyMetadata(null,
FrameworkPropertyMetadataOptions.Inherits, new
PropertyChangedCallback(OnUseGlobalUndoRedoScopeChanged)));
與相關(guān)屬性不同,附加屬性需要使用相關(guān)屬性的RegisterAttached方法注冊。
在注冊的時候同樣需要使用FrameworkPropertyMetadata類,來說明附加屬性的一些特性。
這里 說明SharedUndoRedoScopeProperty是可以傳遞的(設(shè)定Inherits標(biāo)志),并且設(shè)置了該屬性值發(fā)生改變時要調(diào)用的處理函數(shù)。定義附加屬性時,還要求定義兩個讀取附加屬性的方法,其格式是固定的:
public static void SetSharedUndoRedoScope(DependencyObject depObj, bool
isSet)
{
// never place logic in here, because these methods are not called
when things are done in XAML
depObj.SetValue(SharedUndoRedoScopeProperty, isSet);
}
public static UndoService GetSharedUndoRedoScope(DependencyObject
depObj)
{
// never place logic in here, because these methods are not
called when things are done in XAML
return depObj.GetValue(SharedUndoRedoScopeProperty) as UndoService;
}
- 注意:這兩個方法都有一個Dependency類型的參數(shù),由此可知,附加屬性只能附加到DependencyObject上,而且附加屬性的值存儲在該DependencyObject實例內(nèi)!
理解這一點,是理解附加屬性的關(guān)鍵,這就是附加的由來:
depObj.SetValue(SharedUndoRedoScopeProperty, isSet);
在SharedUndoRedoScopeProperty屬性值發(fā)生變化時,WPF屬性系統(tǒng)會調(diào)用在注冊附加屬性時設(shè)置的回調(diào)函數(shù):
OnUseGlobalUndoRedoScopeChanged在這個回調(diào)函數(shù)中,設(shè)置或去除了相應(yīng)的事項處理函數(shù)。WPF中的傳遞事件是一種新的事件類型,將在第7章深入討論,這里的重點是附加屬性。完整的UndoService類源程序如下:
using System;
using System.Collections.Generic;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Controls.Primitives;
using System.Windows.Documents;
using System.Windows.Input;
namespace Yingbao.Chapter4
{
public class UndoService
{
Stack<UndoOperation> undoStack = new Stack<UndoOperation>();
Stack<UndoOperation> redoStack = new Stack<UndoOperation>();
#region Attached Properties
public static readonly DependencyProperty
SharedUndoRedoScopeProperty =
DependencyProperty.RegisterAttached(
"SharedUndoRedoScope", typeof(UndoService),
typeof(UndoService),
new FrameworkPropertyMetadata(null,
FrameworkPropertyMetadataOptions.Inherits, new
PropertyChangedCallback(
OnUseGlobalUndoRedoScopeChanged)));
public static void SetSharedUndoRedoScope(DependencyObject
depObj, bool isSet)
{
depObj.SetValue(SharedUndoRedoScopeProperty, isSet);
}
public static UndoService GetSharedUndoRedoScope(
DependencyObject depObj)
{
return depObj.GetValue(SharedUndoRedoScopeProperty) as
UndoService;
}
private static void OnUseGlobalUndoRedoScopeChanged(
DependencyObject depObj,
DependencyPropertyChangedEventArgs args)
{
if (depObj is TextBoxBase)
{
if (args.OldValue != null)
{
RemoveEventHandlers(depObj as TextBoxBase,
args.OldValue as UndoService);
}
if (args.NewValue != null)
{
AttachEventHandlers(depObj as TextBoxBase,
args.NewValue as UndoService);
}
}
}
private static void AttachEventHandlers(TextBoxBase textBox,
UndoService service)
{
if (textBox != null && service != null)
{
textBox.AddHandler(CommandManager.PreviewExecutedEvent,
new ExecutedRoutedEventHandler(
service.ExecutedHandler), true);
textBox.TextChanged += new TextChangedEventHandler(
service.TextChangedHandler);
}
}
private static void RemoveEventHandlers(TextBoxBase textBox,
UndoService service)
{
if (textBox != null && service != null)
{
textBox.RemoveHandler(CommandManager.PreviewExecutedEvent,
new ExecutedRoutedEventHandler(
service.ExecutedHandler));
textBox.TextChanged -= new
TextChangedEventHandler(service.TextChangedHandler);
}
}
#endregion
void TextChangedHandler(object sender, TextChangedEventArgs e)
{
this.AddUndoableAction(sender as TextBoxBase, e.UndoAction);
}
private void ExecutedHandler(object sender,
ExecutedRoutedEventArgs e)
{
if (e.Command == ApplicationCommands.Undo)
{
e.Handled = true;
Undo();
}
if (e.Command == ApplicationCommands.Redo)
{
e.Handled = true;
Redo();
}
}
private void AddUndoableAction(TextBoxBase sender,
UndoAction action)
{
if (action == UndoAction.Undo)
{
redoStack.Push(new UndoOperation(sender, action));
}
else
{
if (undoStack.Count > 0)
{
UndoOperation op = undoStack.Peek();
if ((op.Sender == sender) && (action ==
UndoAction.Merge))
{
// no-op
}
else
{
PushUndoOperation(sender, action);
}
}
else
{
PushUndoOperation(sender, action);
}
}
}
private void PushUndoOperation(TextBoxBase sender,
UndoAction action)
{
undoStack.Push(new UndoOperation(sender, action));
System.Diagnostics.Debug.WriteLine("PUSHED");
}
public void Undo()
{
if (undoStack.Count > 0)
{
UndoOperation op = undoStack.Pop();
op.Sender.Undo();
op.Sender.Focus();
}
}
public void Redo()
{
if (redoStack.Count > 0)
{
UndoOperation op = redoStack.Pop();
op.Sender.Redo();
op.Sender.Focus();
}
}
}
public class UndoOperation
{
public TextBoxBase Sender;
public UndoAction Action;
public UndoOperation(TextBoxBase sender, UndoAction action)
{
this.Sender = sender;
this.Action = action;
}
}
}
- 有了這個UndoService類,就可以對在窗口中的多個TextBox元素進(jìn)行統(tǒng)一管理了。下面是使用UndoService的XAML的一個示例:
<Window x:Class="Yingbao.Chapter4.AttachedPropertyWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="UndoRedo" Height="600" Width="400"
xmlns:undo="clr-namespace:Yingbao.Chapter4">
<StackPanel>
<Border BorderBrush="Orange" BorderThickness="3" Margin="5"
CornerRadius="2">
<Grid Background="#feca00" >
<undo:UndoService.SharedUndoRedoScope>
<undo:UndoService />
</undo:UndoService.SharedUndoRedoScope>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="75" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="25" />
<RowDefinition Height="25" />
<RowDefinition Height="25" />
<RowDefinition Height="25" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<TextBlock Text="設(shè)定重做范圍" FontSize="22" Foreground="#58290a"
Grid.ColumnSpan="2" />
<TextBlock Grid.Row="1" Grid.Column="0">姓名:</TextBlock>
<TextBox Grid.Row="1" Grid.Column="1" Margin="1" />
<TextBlock Grid.Row="2" Grid.Column="0">畢業(yè)院校:</TextBlock>
<TextBox Grid.Row="2" Grid.Column="1" Margin="1" />
<TextBlock Grid.Row="3" Grid.Column="0">工作簡歷:</TextBlock>
<RichTextBox Grid.Column="0" Grid.Row="4" Grid.ColumnSpan="2"
Margin="1" Height="70" />
</Grid>
</Border>
<Border BorderBrush="#58290a" BorderThickness="3" Margin="5"
CornerRadius="2">
<Border.Resources>
<!-- undo managers can also be created and used as resources -->
<undo:UndoService x:Key="undoService" />
</Border.Resources>
<Grid undo:UndoService.SharedUndoRedoScope="{StaticResource
undoService}" Background="LightGray " >
<Grid.ColumnDefinitions>
<ColumnDefinition Width="75" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="25" />
<RowDefinition Height="25" />
<RowDefinition Height="25" />
<RowDefinition Height="25" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<TextBlock Text="設(shè)定重做范圍" FontSize="22" Foreground="#58290a"
Grid.ColumnSpan="2" />
<TextBlock Grid.Row="1" Grid.Column="0">姓名:</TextBlock>
<TextBox Grid.Row="1" Grid.Column="1" Margin="1" />
<TextBlock Grid.Row="2" Grid.Column="0">畢業(yè)院校:</TextBlock>
<TextBox Grid.Row="2" Grid.Column="1" Margin="1" />
<TextBlock Grid.Row="3" Grid.Column="0">工作簡歷:</TextBlock>
<RichTextBox Grid.Column="0" Grid.Row="4" Grid.ColumnSpan="2"
Margin="1" Height="70" />
</Grid>
</Border>
<Border BorderBrush="DarkGreen" BorderThickness="3" Margin="5"
CornerRadius="2">
<Grid Background="LightGreen" >
<Grid.ColumnDefinitions>
<ColumnDefinition Width="75" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="25" />
<RowDefinition Height="25" />
<RowDefinition Height="25" />
<RowDefinition Height="25" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<TextBlock Text="未設(shè)定重做范圍" FontSize="22"
Foreground="DarkGreen" Grid.ColumnSpan="2" />
<TextBlock Grid.Row="1" Grid.Column="0">姓名:</TextBlock>
<TextBox Grid.Row="1" Grid.Column="1" Margin="1" />
<TextBlock Grid.Row="2" Grid.Column="0">畢業(yè)院校:</TextBlock>
<TextBox Grid.Row="2" Grid.Column="1" Margin="1" />
<TextBlock Grid.Row="3" Grid.Column="0">工作簡歷:</TextBlock>
<RichTextBox Grid.Column="0" Grid.Row="4" Grid.ColumnSpan="2"
Margin="1" Height="70" />
</Grid>
</Border>
</StackPanel>
</Window>
-
圖4-7是這段程序的運行結(jié)果,這個程序可以作為人事部門管理軟件的基本界面。
需要指出的是:要對“取消/重做”功能進(jìn)行測試,要用到標(biāo)準(zhǔn)的快捷鍵?!叭∠钡目旖萱I是“Ctrl+Z”;“重做”的快捷鍵是“Ctrl+Y”。這些快捷鍵在WPF中是內(nèi)嵌的,不需要我們做什么工作。在上面的窗口中,前兩個輸入框都使用了SharedUndoRedoScope屬性,而最后一個輸入框沒有使用SharedUndoRedoScope屬性。
在前兩個輸入框的姓名、畢業(yè)院校、工作簡歷中輸入信息,然后用快捷鍵“Ctrl+Z”和“Ctrl+Y”進(jìn)行“取消/重做”操作,可以看到,這種操作對這三個TextBox都有效。而在最后一組中,這種操作只對具有當(dāng)前輸入焦點的TextBox有效。
圖4-7 使用附加屬性實現(xiàn)的“取消/重做”功能
