附加屬性2

書名: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)的“取消/重做”功能
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • """1.個性化消息: 將用戶的姓名存到一個變量中,并向該用戶顯示一條消息。顯示的消息應(yīng)非常簡單,如“Hello ...
    她即我命閱讀 4,971評論 0 6
  • 1、expected an indented block 冒號后面是要寫上一定的內(nèi)容的(新手容易遺忘這一點); 縮...
    庵下桃花仙閱讀 1,075評論 1 2
  • 一、工具箱(多種工具共用一個快捷鍵的可同時按【Shift】加此快捷鍵選取)矩形、橢圓選框工具 【M】移動工具 【V...
    墨雅丫閱讀 1,494評論 0 0
  • 跟隨樊老師和伙伴們一起學(xué)習(xí)心理知識提升自已,已經(jīng)有三個月有余了,這一段時間因為天氣的原因休課,順便整理一下之前學(xué)習(xí)...
    學(xué)習(xí)思考行動閱讀 964評論 0 2
  • 一臉憤怒的她躺在了床上,好幾次甩開了他抱過來的雙手,到最后還堅決的翻了個身,只留給他一個冷漠的背影。 多次嘗試抱她...
    海邊的藍(lán)兔子閱讀 974評論 1 4

友情鏈接更多精彩內(nèi)容