• ReactiveX 学习笔记(32)使用 Rx.NET + ReactiveUI 实现自定义控件


    课题

    1. 自定义控件内部包含一个用于显示或设置时间的 DateTimeUpDown 控件(Extended.Wpf.Toolkit)。
    2. 自定义控件内部的 DateTimeUpDown 控件的格式为 "HH:mm:ss",即用户不能设置日期,只能设置时分秒。
      该控件的初值是 00:00:00,上下限为 00:00:00~23:59:59。
    3. 自定义控件向外界公开一个可读可写可绑定的名为 Value 的属性,其含义为控件的时分秒所代表的的秒数:时 * 3600 + 分 * 60 + 秒。
      Value 属性的初值是0,上下限为 0~86399。
    4. 在窗口中测试自定义控件时将 Value 属性的值设为 86399,此时内部的 DateTimeUpDown 控件应该显示 23:59:59。

    创建工程

    打开 Visual Studio,File / New / Project...
    新建一个名为 RxExample 的 WPF 应用程序。

    ReactiveUI

    打开 NuGet 包管理器,搜索并安装以下这些包。
    ReactiveUI
    ReactiveUI.Fody
    ReactiveUI.WPF
    Extended.Wpf.Toolkit

    TimeSpanUpDown

    在工程中添加名为 TimeSpanUpDown 的 UserControl 子类(添加新项目,选择 User Control)。

    将 TimeSpanUpDown.xaml 内容改为

    <UserControl
                 xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
                 xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
                 xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
                 xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
                 xmlns:local="clr-namespace:RxExample"
                 xmlns:xctk="http://schemas.xceed.com/wpf/xaml/toolkit" x:Class="RxExample.TimeSpanUpDown"
                 mc:Ignorable="d" 
                 d:DataContext="{d:DesignInstance Type=local:TimeSpanUpDownViewModel, IsDesignTimeCreatable=True}"
                 d:DesignHeight="20" d:DesignWidth="80">
        <Grid x:Name="grid">
            <xctk:DateTimeUpDown Value="{Binding Value}" Format="Custom" FormatString="HH:mm:ss" Maximum="01/01/2000 23:59:59" Minimum="01/01/2000 00:00:00"/>
        </Grid>
    </UserControl>
    

    控件界面通过 XAML 来实现

    • xctk 为 Extended.Wpf.Toolkit 控件包的名字空间。
    • 内部 DateTimeUpDown 控件的 Value 属性被绑定为控件内部的 Value 属性。
    • 内部 DateTimeUpDown 控件的上下限为2000年1月1日的 00:00:00~23:59:59。
      这里日期不重要,主要取时间,但日期必须固定为某一天。
    • 内部 DateTimeUpDown 控件的格式为 "HH:mm:ss",即用户不能设置日期,只能设置时分秒。
    • Grid 标签被取名为 grid,是为了便于设置内部 DateTimeUpDown 控件的 DataContext。

    将 TimeSpanUpDown.xaml.cs 内容改为

    using ReactiveUI;
    using ReactiveUI.Fody.Helpers;
    using System;
    using System.Reactive.Linq;
    using System.Windows;
    using System.Windows.Controls;
    
    namespace RxExample
    {
        public partial class TimeSpanUpDown : UserControl
        {
            public TimeSpanUpDownViewModel VM { get; } = new TimeSpanUpDownViewModel();
    
            public int Value
            {
                get => (int)GetValue(ValueProperty);
                set => SetValue(ValueProperty, value);
            }
    
            public static readonly DependencyProperty ValueProperty =
                DependencyProperty.Register("Value", typeof(int), typeof(TimeSpanUpDown),
                    new FrameworkPropertyMetadata(0, FrameworkPropertyMetadataOptions.BindsTwoWayByDefault, ValuePropertyChangedCallback));
    
            private static void ValuePropertyChangedCallback(DependencyObject d, DependencyPropertyChangedEventArgs e)
            {
                var control = d as TimeSpanUpDown;
                control.VM.SetValue(control.Value);
            }
    
            public TimeSpanUpDown()
            {
                InitializeComponent();
                grid.DataContext = VM;
                VM.ValueChanged.Subscribe(value => SetValue(ValueProperty, value));
            }
        }
        public class TimeSpanUpDownViewModel : ReactiveObject
        {
            [Reactive]
            public DateTime Value { get; set; } = new DateTime(2000, 1, 1);
            public IObservable<int> ValueChanged;
            public void SetValue(int value)
            {
                value = Math.Max(0, Math.Min(86399, value));
                var h = value / 3600;
                var m = value / 60 % 60;
                var s = value % 60;
                Value = new DateTime(2000, 1, 1, h, m, s);
            }
            public TimeSpanUpDownViewModel()
            {
                ValueChanged = this.WhenAnyValue(x => x.Value, (DateTime v) => v.Hour * 3600 + v.Minute * 60 + v.Second);
            }
        }
    }
    

    第37—54行实现了 ViewModel 类,用于绑定内部的 DateTimeUpDown 控件。

    • 第39—40行声明了日期类型的内部 Value 属性,用于绑定内部 DateTimeUpDown 控件的 Value 属性。
      内部 Value 属性的初值为 2000年1月1日 00:00:00。这里日期不重要,主要取时间。
    • 第41行声明了 IObservable<int> 类型的 ValueChanged 属性,相当于一个内部事件。
    • 第42—49行声明了 SetValue 方法,将外部 Value 属性的值传递给内部 Value 属性。
      外部 Value 属性的值即秒数被限定为 0-86399,此秒数被转换为2000年1月1日的某时某分某秒,然后再被设为内部 Value 属性的值。
    • 第39—40行实现了 ValueChanged 事件,当内部 Value 属性发生变化时,将它所代表的的秒数传给外部 Value 属性。

    第10—36行实现了控件主体 TimeSpanUpDown 类。

    • 第12行声明了 ViewModel 类的实例。
    • 第14—28行实现了向外部公开的 Value 属性,根据 WPF 的要求,可绑定的属性必须是依赖属性。
      外部 Value 属性的值为内部 DateTimeUpDown 控件所代表的秒数,因此类型是 int。
      外部 Value 属性的初值为 0。
      外部 Value 属性缺省为双向绑定,可读可写。
      外部 Value 属性发生变化时,通过 ValuePropertyChangedCallback 回调函数将值传递给内部 Value 属性。具体实现为调用 ViewModel 类的 SetValue 方法。
    • 第30—35行为构造器,除了初始化自身外,还做了以下两件事
      将 ViewModel 类的实例作为自身的 DataContext,也就是将该实例绑定到内部的 DateTimeUpDown 控件。
      响应 ViewModel 类的 ValueChanged 事件,将内部 Value 属性的值反映为外部 Value 属性。

    测试自定义控件

    在 MainWindow.xaml 中添加以下两个标签

    <local:TimeSpanUpDown x:Name="x" Value="86399" />
    <TextBlock x:Name="y" Text="{Binding ElementName=x,Path=Value}" />
    

    程序运行后可以发现名为 y 的文本框会实时反映名为 x 的自定义控件中所代表的秒数

  • 相关阅读:
    "废物利用"也抄袭——“完全”DIY"绘图仪"<三、上位机程序设计>
    "废物利用"也抄袭——“完全”DIY"绘图仪"<二、下位机程序设计>
    "废物利用"也抄袭——“完全”DIY"绘图仪"<一、准备工作>
    我还活着,我回来了
    链表的基础操作专题小归纳
    文件的基础操作专题小归纳
    正整数序列 Help the needed for Dexter ,UVa 11384
    偶数矩阵 Even Parity,UVa 11464
    洛谷-跑步-NOI导刊2010提高
    洛谷-关押罪犯-NOIP2010提高组复赛
  • 原文地址:https://www.cnblogs.com/zwvista/p/12931952.html
Copyright © 2020-2023  润新知