WPF customize via three combied custom controls

//D:\C\WpfApp12\NumUpDown.cs
using System;
using System.Collections.Generic;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;

namespace WpfApp12
{
    /// <summary>
    /// Follow steps 1a or 1b and then 2 to use this custom control in a XAML file.
    ///
    /// Step 1a) Using this custom control in a XAML file that exists in the current project.
    /// Add this XmlNamespace attribute to the root element of the markup file where it is 
    /// to be used:
    ///
    ///     xmlns:MyNamespace="clr-namespace:WpfApp12"
    ///
    ///
    /// Step 1b) Using this custom control in a XAML file that exists in a different project.
    /// Add this XmlNamespace attribute to the root element of the markup file where it is 
    /// to be used:
    ///
    ///     xmlns:MyNamespace="clr-namespace:WpfApp12;assembly=WpfApp12"
    ///
    /// You will also need to add a project reference from the project where the XAML file lives
    /// to this project and Rebuild to avoid compilation errors:
    ///
    ///     Right click on the target project in the Solution Explorer and
    ///     "Add Reference"->"Projects"->[Browse to and select this project]
    ///
    ///
    /// Step 2)
    /// Go ahead and use your control in the XAML file.
    ///
    ///     <MyNamespace:NumUpDown/>
    ///
    /// </summary>
    public class NumUpDown : Control
    {
        private Button upBtn, downBtn;


        static NumUpDown()
        {
            DefaultStyleKeyProperty.OverrideMetadata(typeof(NumUpDown), new FrameworkPropertyMetadata(typeof(NumUpDown)));
        }

        public override void OnApplyTemplate()
        {
            base.OnApplyTemplate();
            var tempUpBtn = GetTemplateChild("PART_UpBtn") as Button;
            if (tempUpBtn != null)
            {
                upBtn = tempUpBtn;
                upBtn.Click += UpBtn_Click;
            }

            var tempDownBtn = GetTemplateChild("PART_DownBtn") as Button;
            if (tempDownBtn != null)
            {
                downBtn = tempDownBtn;
                downBtn.Click += DownBtn_Click;
            }
        }


        private void DownBtn_Click(object sender, RoutedEventArgs e)
        {
            if (--Num < MinNum)
            {
                Num = MaxNum;
            }
            NumStr = $"{Num:D2}";
        }

        private void UpBtn_Click(object sender, RoutedEventArgs e)
        {
            if (++Num > MaxNum)
            {
                Num = MinNum;
            }
            NumStr = $"{Num:D2}";
        }
        public int Num
        {
            get { return (int)GetValue(NumProperty); }
            set { SetValue(NumProperty, value); }
        }

        // Using a DependencyProperty as the backing store for Num.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty NumProperty =
            DependencyProperty.Register(nameof(Num), typeof(int), typeof(NumUpDown),
                new PropertyMetadata(0, OnNumChanged));

        private static void OnNumChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {

        }

        public int MaxNum
        {
            get { return (int)GetValue(MaxNumProperty); }
            set { SetValue(MaxNumProperty, value); }
        }

        // Using a DependencyProperty as the backing store for MaxNum.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty MaxNumProperty =
            DependencyProperty.Register(nameof(MaxNum),
                typeof(int),
                typeof(NumUpDown),
                new PropertyMetadata(0));




        public int MinNum
        {
            get { return (int)GetValue(MinNumProperty); }
            set { SetValue(MinNumProperty, value); }
        }

        // Using a DependencyProperty as the backing store for MinNum.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty MinNumProperty =
            DependencyProperty.Register(nameof(MinNum), typeof(int), typeof(NumUpDown), new PropertyMetadata(0));




        public string NumStr
        {
            get { return (string)GetValue(NumStrProperty); }
            set { SetValue(NumStrProperty, value); }
        }

        // Using a DependencyProperty as the backing store for NumStr.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty NumStrProperty =
            DependencyProperty.Register(nameof(NumStr),
                typeof(string),
                typeof(NumUpDown),
                new PropertyMetadata("00"));





        public string ControlName
        {
            get { return (string)GetValue(ControlNameProperty); }
            set { SetValue(ControlNameProperty, value); }
        }

        // Using a DependencyProperty as the backing store for ControlName.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty ControlNameProperty =
            DependencyProperty.Register(nameof(ControlName),
                typeof(string),
                typeof(NumUpDown),
                new PropertyMetadata(""));


    }
}



//D:\C\WpfApp12\TimePicker.cs
using System;
using System.Collections.Generic;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;

namespace WpfApp12
{
    /// <summary>
    /// Follow steps 1a or 1b and then 2 to use this custom control in a XAML file.
    ///
    /// Step 1a) Using this custom control in a XAML file that exists in the current project.
    /// Add this XmlNamespace attribute to the root element of the markup file where it is 
    /// to be used:
    ///
    ///     xmlns:MyNamespace="clr-namespace:WpfApp12"
    ///
    ///
    /// Step 1b) Using this custom control in a XAML file that exists in a different project.
    /// Add this XmlNamespace attribute to the root element of the markup file where it is 
    /// to be used:
    ///
    ///     xmlns:MyNamespace="clr-namespace:WpfApp12;assembly=WpfApp12"
    ///
    /// You will also need to add a project reference from the project where the XAML file lives
    /// to this project and Rebuild to avoid compilation errors:
    ///
    ///     Right click on the target project in the Solution Explorer and
    ///     "Add Reference"->"Projects"->[Browse to and select this project]
    ///
    ///
    /// Step 2)
    /// Go ahead and use your control in the XAML file.
    ///
    ///     <MyNamespace:TimePicker/>
    ///
    /// </summary>
    public class TimePicker : Control
    {
        static TimePicker()
        {
            DefaultStyleKeyProperty.OverrideMetadata(typeof(TimePicker), new FrameworkPropertyMetadata(typeof(TimePicker)));
        }

        public override void OnApplyTemplate()
        {
            base.OnApplyTemplate();
        }

        public int HourNum
        {
            get { return (int)GetValue(HourNumProperty); }
            set { SetValue(HourNumProperty, value); }
        }

        // Using a DependencyProperty as the backing store for HourNum.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty HourNumProperty =
            DependencyProperty.Register(nameof(HourNum), typeof(int), typeof(TimePicker), new PropertyMetadata(0,OnHourNumChanged));

        private static void OnHourNumChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            var tmPicker=d as TimePicker;
            if(tmPicker!=null)
            {
                tmPicker.TimeStr = $"{tmPicker.HourNum:D2}:{tmPicker.MinuteNum:D2}:{tmPicker.SecondNum:D2}";
            }
        }

        public int MinuteNum
        {
            get { return (int)GetValue(MinuteNumProperty); }
            set { SetValue(MinuteNumProperty, value); }
        }

        // Using a DependencyProperty as the backing store for MinuteNum.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty MinuteNumProperty =
            DependencyProperty.Register(nameof(MinuteNum), 
                typeof(int), 
                typeof(TimePicker), 
                new PropertyMetadata(0,OnMinuteNumChanged));

        private static void OnMinuteNumChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            var tmPicker = d as TimePicker;
            if (tmPicker != null)
            {
                tmPicker.TimeStr = $"{tmPicker.HourNum:D2}:{tmPicker.MinuteNum:D2}:{tmPicker.SecondNum:D2}";
            }
        }

        public int SecondNum
        {
            get { return (int)GetValue(SecondNumProperty); }
            set { SetValue(SecondNumProperty, value); }
        }

        // Using a DependencyProperty as the backing store for SecondNum.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty SecondNumProperty =
            DependencyProperty.Register(nameof(SecondNum), 
                typeof(int), 
                typeof(TimePicker),
                new PropertyMetadata(0, OnSecondNumChanged));

        private static void OnSecondNumChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            var tmPicker = d as TimePicker;
            if (tmPicker != null)
            {
                tmPicker.TimeStr = $"{tmPicker.HourNum:D2}:{tmPicker.MinuteNum:D2}:{tmPicker.SecondNum:D2}";
            }
        }

        public string TimeStr
        {
            get { return (string)GetValue(TimeStrProperty); }
            set { SetValue(TimeStrProperty, value); }
        }

        // Using a DependencyProperty as the backing store for TimeStr.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty TimeStrProperty =
            DependencyProperty.Register(nameof(TimeStr), 
                typeof(string), 
                typeof(TimePicker), 
                new PropertyMetadata("00:00:00"));


    }
}

//D:\C\WpfApp12\Themes\Generic.xaml
<ResourceDictionary
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="clr-namespace:WpfApp12">


    <Style TargetType="{x:Type local:NumUpDown}">        
        <Setter Property="Template">
            <Setter.Value>
                <ControlTemplate TargetType="{x:Type local:NumUpDown}">
                    <Border Background="{TemplateBinding Background}"
                            BorderBrush="{TemplateBinding BorderBrush}"
                            BorderThickness="{TemplateBinding BorderThickness}">
                        <Grid Width="90" Height="70">
                            <Grid.Resources>
                                <Style TargetType="TextBlock">
                                    <Setter Property="HorizontalAlignment" Value="Center"/>
                                    <Setter Property="TextAlignment" Value="Center"/>
                                    <Setter Property="VerticalAlignment" Value="Center"/>
                                    <Setter Property="FontSize" Value="20"/>
                                </Style>
                            </Grid.Resources>
                            <Grid.RowDefinitions>
                                <RowDefinition/>
                                <RowDefinition/>
                                <RowDefinition/>
                            </Grid.RowDefinitions>
                            <Grid.ColumnDefinitions>
                                <ColumnDefinition Width="3*"/>
                                <ColumnDefinition Width="*"/>
                            </Grid.ColumnDefinitions>
                            <TextBlock Grid.Row="1"
                                       Grid.Column="0"
                                       HorizontalAlignment="Right"
                                       Text="{Binding Path=ControlName,RelativeSource={RelativeSource AncestorType={x:Type local:NumUpDown}}}"/>
                            <Button x:Name="PART_UpBtn"
                                    Grid.Row="0"
                                    Grid.Column="1"
                                    ToolTip="{Binding Path=MaxNum,RelativeSource={RelativeSource AncestorType={x:Type local:NumUpDown}}}">
                                <Path Data="M0,8 L8,0 L16,8 Z"
                                      Fill="Black"
                                      Width="10"
                                      Height="10"
                                      Stretch="Uniform"/>                             
                            </Button>
                            
                            <TextBlock Grid.Row="1"
                                       Grid.Column="1"
                                       Text="{Binding Path=NumStr,RelativeSource={RelativeSource AncestorType={x:Type local:NumUpDown}}}"/>
                            
                            <Button x:Name="PART_DownBtn"
                                    Grid.Row="2"
                                    Grid.Column="1"
                                    ToolTip="{Binding Path=MinNum,RelativeSource={RelativeSource AncestorType={x:Type local:NumUpDown}}}">
                                <Path Data="M0,0 L8,8 L16,0 Z"
                                      Fill="Black"
                                      Width="10"
                                      Height="10"
                                      Stretch="Uniform"/>
                            </Button>
                        </Grid>
                    </Border>
                </ControlTemplate>
            </Setter.Value>
        </Setter>
    </Style>

    <Style TargetType="{x:Type local:TimePicker}">
        <Setter Property="Template">
            <Setter.Value>
                <ControlTemplate TargetType="{x:Type local:TimePicker}">
                    <Border Background="{TemplateBinding Background}"
                            BorderBrush="{TemplateBinding BorderBrush}"
                            BorderThickness="{TemplateBinding BorderThickness}">
                        <Grid Height="110" Width="300">
                            <Grid.Resources>
                                <Style TargetType="TextBlock">
                                    <Setter Property="HorizontalAlignment" Value="Center"/>
                                    <Setter Property="VerticalAlignment" Value="Center"/>
                                    <Setter Property="FontSize" Value="30"/>
                                </Style>
                            </Grid.Resources>
                            <Grid.RowDefinitions>
                                <RowDefinition Height="*"/>
                                <RowDefinition Height="3*"/>
                            </Grid.RowDefinitions>
                            <Grid.ColumnDefinitions>
                                <ColumnDefinition/>
                                <ColumnDefinition/>
                                <ColumnDefinition/>
                            </Grid.ColumnDefinitions>
                            <TextBlock Grid.Row="0" Grid.Column="0" Text="Time:"/>
                            <TextBlock Grid.Row="0" Grid.Column="1" Grid.ColumnSpan="2"
                                       Text="{Binding Path=TimeStr,RelativeSource={RelativeSource AncestorType={x:Type local:TimePicker}}}"/>
                            <local:NumUpDown MaxNum="23" MinNum="0" ControlName="Hour:"
                                             Grid.Row="1" Grid.Column="0"
                                             Num="{Binding Path=HourNum,Mode=TwoWay,RelativeSource={RelativeSource AncestorType={x:Type local:TimePicker}}}"/>

                            <local:NumUpDown MaxNum="59" MinNum="0" ControlName="Minute:"
                                             Grid.Row="1" Grid.Column="1"
                                             Num="{Binding Path=MinuteNum,Mode=TwoWay,RelativeSource={RelativeSource AncestorType={x:Type local:TimePicker}}}"/>

                            <local:NumUpDown MaxNum="59" MinNum="0" ControlName="Second:"
                                             Grid.Row="1" Grid.Column="2"
                                             Num="{Binding Path=SecondNum,Mode=TwoWay,RelativeSource={RelativeSource AncestorType={x:Type local:TimePicker}}}"/>
                        </Grid>
                    </Border>                    
                </ControlTemplate>
            </Setter.Value>
        </Setter>
    </Style>
</ResourceDictionary>


//D:\C\WpfApp12\MainWindow.xaml
<Window x:Class="WpfApp12.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:WpfApp12"
        mc:Ignorable="d"
        Title="MainWindow" Height="450" Width="800">
    <Grid>
        <StackPanel Height="100" Width="300"
                    Orientation="Horizontal">
            <local:TimePicker/>
        </StackPanel>
    </Grid>
</Window>


 

 

 

 

 

image

 

 

 

 

 

image

 

 

 

 

 

 

 

 

 

 

image

 

posted @ 2026-09-06 16:10  FredGrit  阅读(6)  评论(0)    收藏  举报