附加属性2

书名:WPF专业编程指南
作者:李应保
出版社:电子工业出版社
出版时间:2010-01
ISBN:9787121100116


一、附加属性实例

  • 这个例子中使用附加属性来实现编辑软件中常用的“重做/取消”功能。
    WPF的Text Box及相关元素都内在支持“重做/取消”和“拷贝/复制”这样一些常用的编辑功能,但WPF的“重做/取消”功能只对具有当前输入焦点的TextBox有效。
    在这里要做的是:“取消和重做”可以对多个TextBox同时进行。

思路:

  • 为此,要把用户的操作用堆栈管理起来,当用户输入的时候,把用户的操作放入堆栈,当用户要取消一个输入的时候可把该输入放到另外一堆栈;
    在用户要进行重做时,只要把用户的输入从另外一个堆栈中取出来就可以了。
    由于堆栈的先入先出功能,可以很方便地管理用户“最近”的 操作。

设计

  • 首先我们定义一个UndoOperation操作类:
  public class UndoOperation
    {
      public TextBoxBase Sender;
      public UndoAction Action;
      public UndoOperation(TextBoxBase sender, UndoAction action)
      {
          this.Sender = sender;
            this.Action = action;
        }
      }
  • 这个类中有两个域,一个是Sender表示当前操作哪一个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>();
  • 如前所述,附加属性是相关属性的一种。和相关属性一样,需要定义一个public static类型为DependencyProperty的变量:
  public static readonly DependencyProperty SharedUndoRedoScopeProperty =
            DependencyProperty.RegisterAttached("SharedUndoRedoScope",
            typeof(UndoService),
            typeof(UndoService),
            new FrameworkPropertyMetadata(null,
            FrameworkPropertyMetadataOptions.Inherits, new
            PropertyChangedCallback(OnUseGlobalUndoRedoScopeChanged)));
  • 与相关属性不同,附加属性需要使用相关属性的RegisterAttached方法注册。
    在注册的时候同样需要使用FrameworkPropertyMetadata类,来说明附加属性的一些特性。
    这里 说明SharedUndoRedoScopeProperty是可以传递的(设定Inherits标志),并且设置了该属性值发生改变时要调用的处理函数。

  • 定义附加属性时,还要求定义两个读取附加属性的方法,其格式是固定的:

  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类型的参数,由此可知,附加属性只能附加到DependencyObject上,而且附加属性的值存储在该DependencyObject实例内!
    理解这一点,是理解附加属性的关键,这就是附加的由来:
  depObj.SetValue(SharedUndoRedoScopeProperty, isSet);
  • 在SharedUndoRedoScopeProperty属性值发生变化时,WPF属性系统会调用在注册附加属性时设置的回调函数:
    OnUseGlobalUndoRedoScopeChanged

  • 在这个回调函数中,设置或去除了相应的事项处理函数。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元素进行统一管理了。下面是使用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="设定重做范围" 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">毕业院校:</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="设定重做范围" 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">毕业院校:</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="未设定重做范围" 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">毕业院校:</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是这段程序的运行结果,这个程序可以作为人事部门管理软件的基本界面。
    需要指出的是:要对“取消/重做”功能进行测试,要用到标准的快捷键。“取消”的快捷键是“Ctrl+Z”;“重做”的快捷键是“Ctrl+Y”。这些快捷键在WPF中是内嵌的,不需要我们做什么工作。在上面的窗口中,前两个输入框都使用了SharedUndoRedoScope属性,而最后一个输入框没有使用SharedUndoRedoScope属性。
    在前两个输入框的姓名、毕业院校、工作简历中输入信息,然后用快捷键“Ctrl+Z”和“Ctrl+Y”进行“取消/重做”操作,可以看到,这种操作对这三个TextBox都有效。而在最后一组中,这种操作只对具有当前输入焦点的TextBox有效。


    图4-7 使用附加属性实现的“取消/重做”功能
©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

相关阅读更多精彩内容

  • """1.个性化消息: 将用户的姓名存到一个变量中,并向该用户显示一条消息。显示的消息应非常简单,如“Hello ...
    她即我命阅读 4,196评论 0 6
  • 1、expected an indented block 冒号后面是要写上一定的内容的(新手容易遗忘这一点); 缩...
    庵下桃花仙阅读 783评论 0 2
  • 一、工具箱(多种工具共用一个快捷键的可同时按【Shift】加此快捷键选取)矩形、椭圆选框工具 【M】移动工具 【V...
    墨雅丫阅读 1,240评论 0 0
  • 跟随樊老师和伙伴们一起学习心理知识提升自已,已经有三个月有余了,这一段时间因为天气的原因休课,顺便整理一下之前学习...
    学习思考行动阅读 720评论 0 2
  • 一脸愤怒的她躺在了床上,好几次甩开了他抱过来的双手,到最后还坚决的翻了个身,只留给他一个冷漠的背影。 多次尝试抱她...
    海边的蓝兔子阅读 612评论 0 4

友情链接更多精彩内容