æ¨æ¥ã®çºå±åãControlTemplateã®Canvasã®ä¸ã«Ellipseã¨TextBlockãå
¥ãã¦ã¿ã
çµæ
æå¾
éãããããã
ã¦ã¼ãã¥ã¼ã
youtu.be
ãã¹ãã¢ããªã®GIFã¢ãã¡ã¼ã·ã§ã³ä¸è¦§
ãã¹ãã¢ããªã®ã³ã¼ã
2025WPF/20250117_EllipseCanvasThumb at main · gogowaten/2025WPF
ãã¹ãç°å¢
- Windows 10 Home ãã¼ã¸ã§ã³ 22H2
- Visual Studio Community 2022 Version 17.12.4
- WPF
- C#
- .NET 8.0
CustomControl1.sc
using System.Windows; using System.Windows.Controls; using System.Windows.Controls.Primitives; using System.Windows.Data; using System.Windows.Input; using System.Windows.Media; namespace _20250117_EllipseCanvasThumb { public class CanvasThumb : Thumb { private readonly Thumb MyThumb; private const double MinimumSize = 1; private const double MinimumLocate = 0; private const double ThumbSize = 20; static CanvasThumb() { DefaultStyleKeyProperty.OverrideMetadata(typeof(CanvasThumb), new FrameworkPropertyMetadata(typeof(CanvasThumb))); } public CanvasThumb() { MyThumb = new() { Width = ThumbSize, Height = ThumbSize, Cursor = Cursors.SizeNWSE }; MyThumb.DragDelta += Thumb_DragDelta; DragDelta += Thumb_DragDelta; SetInitialPosition(); } private void SetInitialPosition() { Canvas.SetLeft(MyThumb, MinimumLocate); Canvas.SetTop(MyThumb, MinimumLocate); Canvas.SetLeft(this, MinimumLocate); Canvas.SetTop(this, MinimumLocate); } private void Thumb_DragDelta(object sender, DragDeltaEventArgs e) { if (sender is Thumb t) { if (t == MyThumb) { //æå°ãµã¤ãºæªæºã«ãªããªãããã«Thumbã®ç§»å Canvas.SetLeft(t, Math.Max(MinimumSize, Canvas.GetLeft(t) + e.HorizontalChange)); Canvas.SetTop(t, Math.Max(MinimumSize, Canvas.GetTop(t) + e.VerticalChange)); e.Handled = true; } else if (t == this) { //æå°åº§æ¨æªæºã«ãªããªãããã«èªèº«ã®ç§»å Canvas.SetLeft(t, Math.Max(MinimumLocate, Canvas.GetLeft(t) + e.HorizontalChange)); Canvas.SetTop(t, Math.Max(MinimumLocate, Canvas.GetTop(t) + e.VerticalChange)); e.Handled = true; } } } public override void OnApplyTemplate() { //Templateã®ä¸ã®Canvasãåå¾ãã¦MyThumbã追å ã¨Bindingå¦ç base.OnApplyTemplate(); if (GetTemplateChild("PART_Canvas") is Canvas panel) { panel.Children.Add(MyThumb); //ãã¤ã³ã //èªèº«ã®ãµã¤ãºãã½ã¼ã¹ã«MyThumbã®åº§æ¨ããã¤ã³ã MyThumb.DataContext = this; _ = MyThumb.SetBinding(Canvas.LeftProperty, new Binding(nameof(Width)) { Mode = BindingMode.TwoWay }); _ = MyThumb.SetBinding(Canvas.TopProperty, new Binding(nameof(Height)) { Mode = BindingMode.TwoWay }); } } } public class EllipseThumb : CanvasThumb { public Brush Fill { get { return (Brush)GetValue(FillProperty); } set { SetValue(FillProperty, value); } } public static readonly DependencyProperty FillProperty = DependencyProperty.Register(nameof(Fill), typeof(Brush), typeof(EllipseThumb), new PropertyMetadata(null)); public Brush Stroke { get { return (Brush)GetValue(StrokeProperty); } set { SetValue(StrokeProperty, value); } } public static readonly DependencyProperty StrokeProperty = DependencyProperty.Register(nameof(Stroke), typeof(Brush), typeof(EllipseThumb), new PropertyMetadata(null)); public double StrokeThickness { get { return (double)GetValue(StrokeThicknessProperty); } set { SetValue(StrokeThicknessProperty, value); } } public static readonly DependencyProperty StrokeThicknessProperty = DependencyProperty.Register(nameof(StrokeThickness), typeof(double), typeof(EllipseThumb), new PropertyMetadata(1.0)); static EllipseThumb() { DefaultStyleKeyProperty.OverrideMetadata(typeof(EllipseThumb), new FrameworkPropertyMetadata(typeof(EllipseThumb))); } public EllipseThumb() { } } public class EllipseTextThumb : EllipseThumb { public Brush TextBackground { get { return (Brush)GetValue(TextBackgroundProperty); } set { SetValue(TextBackgroundProperty, value); } } public static readonly DependencyProperty TextBackgroundProperty = DependencyProperty.Register(nameof(TextBackground), typeof(Brush), typeof(EllipseTextThumb), new PropertyMetadata(null)); public string Text { get { return (string)GetValue(TextProperty); } set { SetValue(TextProperty, value); } } public static readonly DependencyProperty TextProperty = DependencyProperty.Register(nameof(Text), typeof(string), typeof(EllipseTextThumb), new PropertyMetadata(string.Empty)); static EllipseTextThumb() { DefaultStyleKeyProperty.OverrideMetadata(typeof(EllipseTextThumb), new FrameworkPropertyMetadata(typeof(EllipseTextThumb))); } public EllipseTextThumb() { } } }
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:_20250117_EllipseCanvasThumb"> <Style x:Key="canvasT" TargetType="{x:Type local:CanvasThumb}"> <Setter Property="Canvas.Left" Value="0"/> <Setter Property="Canvas.Top" Value="0"/> <Setter Property="Template"> <Setter.Value> <ControlTemplate TargetType="{x:Type local:CanvasThumb}"> <Canvas x:Name="PART_Canvas" Width="{TemplateBinding Width}" Height="{TemplateBinding Height}" Background="{TemplateBinding Background}"> </Canvas> </ControlTemplate> </Setter.Value> </Setter> </Style> <Style TargetType="{x:Type local:EllipseThumb}" BasedOn="{StaticResource canvasT}"> <Setter Property="Template"> <Setter.Value> <ControlTemplate TargetType="{x:Type local:EllipseThumb}"> <Canvas x:Name="PART_Canvas" Width="{TemplateBinding Width}" Height="{TemplateBinding Height}" Background="{TemplateBinding Background}"> <Ellipse Width="{TemplateBinding Width}" Height="{TemplateBinding Height}" Fill="{TemplateBinding Fill}" Stroke="{TemplateBinding Stroke}" StrokeThickness="{TemplateBinding StrokeThickness}"/> </Canvas> </ControlTemplate> </Setter.Value> </Setter> </Style> <Style TargetType="{x:Type local:EllipseTextThumb}" BasedOn="{StaticResource canvasT}"> <Setter Property="Template"> <Setter.Value> <ControlTemplate TargetType="{x:Type local:EllipseTextThumb}"> <Canvas x:Name="PART_Canvas" Width="{TemplateBinding Width}" Height="{TemplateBinding Height}" Background="{TemplateBinding Background}"> <Grid> <Ellipse Width="{TemplateBinding Width}" Height="{TemplateBinding Height}" Fill="{TemplateBinding Fill}" Stroke="{TemplateBinding Stroke}" StrokeThickness="{TemplateBinding StrokeThickness}"/> <TextBlock Text="{TemplateBinding Text}" Background="{TemplateBinding TextBackground}" HorizontalAlignment="Center" VerticalAlignment="Center"/> </Grid> </Canvas> </ControlTemplate> </Setter.Value> </Setter> </Style> </ResourceDictionary>
CanvasThumbã¯ã©ã¹ã¯æ¨æ¥ã®ã³ãã
追å ãããã¯ã©ã¹ã¯2å
EllipseThumb
EllipseTextThumb
EllipseThumb
CanvasThumbãç¶æ¿ãã¦ãä¾åé¢ä¿ããããã£ã3ã¤è¿½å ããã ã
Fillï¼å¡ãã¤ã¶ãã®è²
Strokeï¼åç°ã®è²
StrokeThicknessï¼åç°ã®å¤ªã
æ§é ã¯
CanvasThumbã®ãã®ãã³ãããã¦ãCanvasã®ä¸ã«Ellipseã追å ãBasedOnã§CanvasThumbã®ã¹ã¿ã¤ã«ãç¶æ¿ãTargetTypeã®å¤æ´ãå¿
è¦ãªããããã£ã®Binding
TargetTypeãå¤æ´ãã¦ããã¨ãTemplateBindingã§ã®å
¥ååè£ã«é©åãªãã®ã表示ããã
æå®ç¡ãã ã¨ã¨ã©ã¼ã«ãªã
æå®ããã ã¨è¿½å ããä¾åé¢ä¿ããããã£ãåºã¦ãã
Styleã®TargetTypeã¨ControlTemplateã®TargetTypeã®ä¸¡æ¹ã§ã®æå®ãå¿
è¦
EllipseTextThumb
EllipseThumbãç¶æ¿ãã¦ãä¾åé¢ä¿ããããã£ã追å ããã ã
EllipseThumbã§è¿½å ãã3ã¤ã®ä¾åé¢ä¿ããããã£ããå¼ãç¶ã使ãã
追å ããä¾åé¢ä¿ããããã£ã¯2ã¤
TextBackgroundï¼èæ¯è²
Textï¼ããã¹ã
- Canvas
- Grid
- Ellipse
- TextBlock
- Grid
GridãCanvasã¨ã®éã«å
¥ãããã¨ã§ãTextBlockã®è¡¨ç¤ºä½ç½®ã調ç¯ã§ããããã«ãªãããããGridãå
¥ããªãã¨HorizontalAlignmentã§Centerãæå®ãã¦ãç¡å¹ã«ãªãããªããªãCanvasã§ã®ä½ç½®æå®ã¯Leftã¨Topã§è¡ã£ã¦ãããã
MainWindow.xaml
<Window x:Class="_20250117_EllipseCanvasThumb.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:_20250117_EllipseCanvasThumb" mc:Ignorable="d" Title="MainWindow" Height="450" Width="500"> <Grid> <Grid.ColumnDefinitions> <ColumnDefinition/> <ColumnDefinition Width="150"/> </Grid.ColumnDefinitions> <Canvas> <local:EllipseThumb x:Name="MyRange" Canvas.Left="150" Canvas.Top="30" Width="100" Height="100" Background="Pink" Fill="Lavender" Stroke="Gray" StrokeThickness="5"/> <local:EllipseTextThumb x:Name="EllipseTextThumb" Canvas.Left="0" Canvas.Top="0" Width="100" Height="100" Background="YellowGreen" Fill="Lavender" Stroke="Gray" StrokeThickness="5" Text="TextBlock" TextBackground="Transparent"/> </Canvas> <DockPanel Grid.Column="1"> <GroupBox DockPanel.Dock="Top" Header="{Binding Name}" DataContext="{Binding ElementName=MyRange}"> <StackPanel Margin="5"> <TextBlock Text="{Binding Path=(Canvas.Left), StringFormat=left {0:0.0}}"/> <TextBlock Text="{Binding Path=(Canvas.Top), StringFormat=top {0:0.0}}"/> <TextBlock Text="{Binding Path=ActualWidth, StringFormat=width {0:0.0}}"/> <TextBlock Text="{Binding Path=ActualHeight, StringFormat=height {0:0.0}}"/> </StackPanel> </GroupBox> <GroupBox DockPanel.Dock="Top" Header="{Binding Name}" DataContext="{Binding ElementName=EllipseTextThumb}"> <StackPanel Margin="5"> <TextBlock Text="{Binding Path=(Canvas.Left), StringFormat=left {0:0.0}}"/> <TextBlock Text="{Binding Path=(Canvas.Top), StringFormat=top {0:0.0}}"/> <TextBlock Text="{Binding Path=ActualWidth, StringFormat=width {0:0.0}}"/> <TextBlock Text="{Binding Path=ActualHeight, StringFormat=height {0:0.0}}"/> <TextBlock Text="{Binding Path=Text, StringFormat=Text {0:0.0}}"/> <TextBox Text="{Binding Path=Text, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"/> </StackPanel> </GroupBox> </DockPanel> </Grid> </Window>
DockPanelã®ä¸ã¯åä½ç¢ºèªç¨ãªã®ã§å¿
è¦ãªã
MainWindow.xaml.cs
using System.Windows; namespace _20250117_EllipseCanvasThumb { public partial class MainWindow : Window { public MainWindow() { InitializeComponent(); } } }
ææ³
ã§ãããããã§ã©ããªè¦ç´ ã§ããµã¤ãºå¯å¤ï¼ãã©ãã°ç§»åã«ã§ããã
æåã¯ControlTemplateã®TargetTypeãæå®ãã¦ãããã§ã追å ããä¾åé¢ä¿ããããã£ãåºã¦ããªãã¦ãæåã§å
¥åãããã¨ã©ã¼ã«ãªããã§èºãã¦ãã
é¢é£è¨äº
ååã®WPFè¨äºã¯æ¨æ¥ã®
WPFããµã¤ãºå¯å¤ï¼ãã¦ã¹ãã©ãã°ç§»åå¯è½ãªCanvasããã§ããã ãç°¡æãã«ã«ã¹ã¿ã ã³ã³ããã¼ã«ã§ä½ã£ã¦ã¿ã - åå¾ãã¦ãã®ããã°