åã åããã®ç¶ã
WPFãèªåãµã¤ãºCanvasãGroupThumbã«ä½¿ã£ã¦ã¿ã - åå¾ãã¦ãã®ããã°
gogowaten.hatenablog.com
ä»åã®çµæ
åè¦ç´ ã®ç§»åå¾ã«ã¯è¦ªè¦ç´ ã®ä½ç½®ãå¤æ´(ä¿®æ£)ããå¦çã追å ããã®ã§éåæããªããªã£ã¦ãã
ãã®ä½ç½®å¤æ´å¦çãå¿
è¦ã«ãªãã®ã¯ãåè¦ç´ ã親è¦ç´ ã®å·¦å´ã¨ä¸å´ã®ç¯å²å¤(ãã¤ãã¹åº§æ¨)ã«ãªã£ãã¨ãã§ãä»åã¯ãã®å¦çãåè¦ç´ ã®ç§»åå¾ã«è¡ã£ã¦ãã
ãã¹ãã¢ããªã®ã³ã¼ã
2024WPF/20241212_ReLayoutGroupThumb at master · gogowaten/2024WPF
ç°å¢
- ä½æã¨åä½ç°å¢
- Windows 10 Home ãã¼ã¸ã§ã³ 22H2
- Visual Studio Community 2022 Version 17.12.3
- WPF
- C#
- .NET 8.0
ExCanvas.cs
using System.Windows.Controls; using System.Windows; namespace _20241212_ReLayoutGroupThumb { /// <summary> /// åè¦ç´ ã«åããã¦ãµã¤ãºãå¤åããCanvas /// ãã ããåè¦ç´ ã®ãã¼ã¸ã³ã¨ããã£ã³ã°ã¯èæ ®ãã¦ããªãã /// ArrangeOverrideãç解ãã¦ããªãã®ã§ä¸å ·åããããã /// </summary> public class ExCanvas : Canvas { protected override Size ArrangeOverride(Size arrangeSize) { if (double.IsNaN(Width) && double.IsNaN(Height)) { base.ArrangeOverride(arrangeSize); Size resultSize = new(); foreach (var item in Children.OfType<FrameworkElement>()) { double x = GetLeft(item) + item.ActualWidth; double y = GetTop(item) + item.ActualHeight; if (resultSize.Width < x) resultSize.Width = x; if (resultSize.Height < y) resultSize.Height = y; } return resultSize; } else { return base.ArrangeOverride(arrangeSize); } } } }
åè¦ç´ ã«å¿ãã§ãµã¤ãºèª¿æ´ããCanvasã¯åã
åããã®ã³ãã
CustomControl1.sc
using System.Collections.ObjectModel; using System.Windows; using System.Windows.Controls; using System.Windows.Controls.Primitives; using System.Windows.Data; using System.Windows.Markup; using System.Windows.Media; using System.Collections.Specialized; namespace _20241212_ReLayoutGroupThumb { /// <summary> /// åºç¤Thumbããã¹ã¦ã®CustomControlThumbã®æ´¾çå /// </summary> public abstract class KisoThumb : Thumb { #region ä¾åé¢ä¿ãããã㣠public double MyLeft { get { return (double)GetValue(MyLeftProperty); } set { SetValue(MyLeftProperty, value); } } public static readonly DependencyProperty MyLeftProperty = DependencyProperty.Register(nameof(MyLeft), typeof(double), typeof(KisoThumb), new PropertyMetadata(0.0)); public double MyTop { get { return (double)GetValue(MyTopProperty); } set { SetValue(MyTopProperty, value); } } public static readonly DependencyProperty MyTopProperty = DependencyProperty.Register(nameof(MyTop), typeof(double), typeof(KisoThumb), new PropertyMetadata(0.0)); public string MyText { get { return (string)GetValue(MyTextProperty); } set { SetValue(MyTextProperty, value); } } public static readonly DependencyProperty MyTextProperty = DependencyProperty.Register(nameof(MyText), typeof(string), typeof(KisoThumb), new PropertyMetadata(string.Empty)); #endregion ä¾åé¢ä¿ãããã㣠//親è¦ç´ ã®èå¥ç¨ãèªèº«ãã°ã«ã¼ãåãããã¨ãã«è¦ªè¦ç´ ã®GroupThumbãå ¥ãã¦ãã public GroupThumb? MyParentThumb { get; internal set; } static KisoThumb() { DefaultStyleKeyProperty.OverrideMetadata(typeof(KisoThumb), new FrameworkPropertyMetadata(typeof(KisoThumb))); } public KisoThumb() { DataContext = this; Focusable = true; } } public class TextBlockThumb : KisoThumb { static TextBlockThumb() { DefaultStyleKeyProperty.OverrideMetadata(typeof(TextBlockThumb), new FrameworkPropertyMetadata(typeof(TextBlockThumb))); } } public class EllipseTextThumb : TextBlockThumb { #region ä¾åé¢ä¿ãããã㣠public double MySize { get { return (double)GetValue(MySizeProperty); } set { SetValue(MySizeProperty, value); } } public static readonly DependencyProperty MySizeProperty = DependencyProperty.Register(nameof(MySize), typeof(double), typeof(EllipseTextThumb), new PropertyMetadata(30.0)); #endregion ä¾åé¢ä¿ãããã㣠static EllipseTextThumb() { DefaultStyleKeyProperty.OverrideMetadata(typeof(EllipseTextThumb), new FrameworkPropertyMetadata(typeof(EllipseTextThumb))); } } [ContentProperty(nameof(MyThumbs))] public class GroupThumb : KisoThumb { #region ä¾åé¢ä¿ãããã㣠public ObservableCollection<KisoThumb> MyThumbs { get { return (ObservableCollection<KisoThumb>)GetValue(MyThumbsProperty); } set { SetValue(MyThumbsProperty, value); } } public static readonly DependencyProperty MyThumbsProperty = DependencyProperty.Register(nameof(MyThumbs), typeof(ObservableCollection<KisoThumb>), typeof(GroupThumb), new PropertyMetadata(null)); #endregion ä¾åé¢ä¿ãããã㣠#region ã³ã³ã¹ãã©ã¯ã¿ static GroupThumb() { DefaultStyleKeyProperty.OverrideMetadata(typeof(GroupThumb), new FrameworkPropertyMetadata(typeof(GroupThumb))); } public GroupThumb() { MyThumbs = []; Loaded += GroupThumb_Loaded; MyThumbs.CollectionChanged += MyThumbs_CollectionChanged; FrameworkPropertyMetadata newmeta = new(typeof(GroupThumb)); } #endregion ã³ã³ã¹ãã©ã¯ã¿ #region åæå private void GroupThumb_Loaded(object sender, RoutedEventArgs e) { var temp = GetTemplateChild("PART_ItemsControl"); if (temp is ItemsControl ic) { var ec = GetExCanvas(ic); if (ec != null) { _ = SetBinding(WidthProperty, new Binding() { Source = ec, Path = new PropertyPath(ActualWidthProperty) }); _ = SetBinding(HeightProperty, new Binding() { Source = ec, Path = new PropertyPath(ActualHeightProperty) }); } } } //åè¦ç´ ã辿ã£ã¦ExCanvasãåãåºã private static ExCanvas? GetExCanvas(DependencyObject d) { if (d is ExCanvas canvas) return canvas; for (int i = 0; i < VisualTreeHelper.GetChildrenCount(d); i++) { ExCanvas? c = GetExCanvas(VisualTreeHelper.GetChild(d, i)); if (c is not null) return c; } return null; } private void MyThumbs_CollectionChanged(object? sender, NotifyCollectionChangedEventArgs e) { if (e.Action == NotifyCollectionChangedAction.Add && e.NewItems?[0] is KisoThumb nnt) { nnt.MyParentThumb = this; } else if (e.Action == NotifyCollectionChangedAction.Remove && e.OldItems?[0] is KisoThumb ot) { ot.MyParentThumb = null; } } #endregion åæå /// <summary> /// åé ç½®ãåè¦ç´ ã®åº§æ¨ãå ã«ä½ç½®ãä¿®æ£ãã /// </summary> public void ReLayout() { //ãã¹ã¦ã®åè¦ç´ ã§æãå·¦ä¸ã®åº§æ¨ãåå¾ double left = double.MaxValue; double top = double.MaxValue; foreach (var item in MyThumbs) { if (left > item.MyLeft) { left = item.MyLeft; } if (top > item.MyTop) { top = item.MyTop; } } //èªèº«ã®åº§æ¨ã¨æ¯è¼ãåã(å¤åãªã)ãªãçµäº if (left == MyLeft && top == MyTop) return; //座æ¨å¤åã®å ´åã¯ãèªèº«ã¨å ¨ã¦ã®åè¦ç´ ã®åº§æ¨ãå¤æ´ãã if (left != 0) { foreach (var item in MyThumbs) { item.MyLeft -= left; } MyLeft += left; } if (top != 0) { foreach (var item in MyThumbs) { item.MyTop -= top; } MyTop += top; } //ParentThumbã«ãä¼æããã MyParentThumb?.ReLayout(); } /// <summary> /// ReLayoutã®å·¦ä¸åº§æ¨åå¾ãRectã®Unionçããã¾ãã¡ /// </summary> public void ReLayout2() { //åè¦ç´ ãã¹ã¦ãåã¾ãç¯å²Rectãè¨ç® KisoThumb tt = MyThumbs[0]; Rect uRect = new(tt.MyLeft, tt.MyTop, tt.ActualWidth, tt.ActualHeight); foreach (var item in MyThumbs) { Rect r = new(item.MyLeft, item.MyTop, item.ActualWidth, item.ActualHeight); uRect.Union(r); } //ä»ã®ç¯å²Rectã¨æ¯è¼ã座æ¨å¤åãªããªãçµäº if (uRect.Left == MyLeft && uRect.Top == MyTop) return; //座æ¨å¤åã®å ´åã¯ãèªèº«ã¨å ¨ã¦ã®åè¦ç´ ã®åº§æ¨ãå¤æ´ãã if (uRect.Left != 0) { foreach (var item in MyThumbs) { item.MyLeft -= uRect.Left; } MyLeft += uRect.Left; } if (uRect.Top != 0) { foreach (var item in MyThumbs) { item.MyTop -= uRect.Top; } MyTop += uRect.Top; } //ParentThumbã«ãä¼æããã MyParentThumb?.ReLayout2(); } } }
Thumbãç¶æ¿ããã«ã¹ã¿ã ã³ã³ããã¼ã«ç¾¤ã¯ååããã®ã³ããæ¹å¤
親è¦ç´ ã®ä½ç½®å¤æ´å¦ç
ãããå®è¡ããã®ã¯è¦ªè¦ç´ ã ãã©ããã®ãã£ããã¯åè¦ç´ ã«ããã®ã§ãåè¦ç´ ã¯è¦ªè¦ç´ ãç¥ã£ã¦ããå¿
è¦ããããã£ã¦ãã¨ã§è¦ªè¦ç´ ãå
¥ãã¦ããããããã£ãç¨æãMyParentThumbã£ã¦ååã«ããã194è¡ç®ã¨ãã«ãããã®
ããã§ä½ç½®å¤æ´ããã£ãããæ´ã«è¦ªã®è¦ªã¨é¡ã£ã¦å¦çãããã¨ãã§ãã
MyParentThumbã«è¦ªè¦ç´ ããããã¿ã¤ãã³ã°
親è¦ç´ ãåè¦ç´ ã管çãã¦ããããããã£ã¯MyThumbsãããã¯Collectionåãªã®ã§è¦ç´ å¤æ´æã«åãCollectionChangedã£ã¦ããã¤ãã³ããããã®ã§ããã§è¡ãããã«ãã
åè¦ç´ 追å æã«èªèº«ãåè¦ç´ ã®MyParentThumbã«ç»é²ãããã¯å¿
é ãåé¤æã«MyParentThumbãnullã«ãã¦ãããã©ãå¿
è¦ãªããã
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:_20241212_ReLayoutGroupThumb"> <Style x:Key="kiso" TargetType="{x:Type local:KisoThumb}"> <Setter Property="Canvas.Left" Value="{Binding MyLeft}"/> <Setter Property="Canvas.Top" Value="{Binding MyTop}"/> </Style> <Style TargetType="{x:Type local:TextBlockThumb}" BasedOn="{StaticResource kiso}"> <Setter Property="Template"> <Setter.Value> <ControlTemplate> <TextBlock Text="{Binding MyText}" Background="{TemplateBinding Background}"/> </ControlTemplate> </Setter.Value> </Setter> </Style> <Style TargetType="{x:Type local:EllipseTextThumb}" BasedOn="{StaticResource kiso}"> <Setter Property="Template"> <Setter.Value> <ControlTemplate> <Grid> <Ellipse Width="{Binding MySize}" Height="{Binding MySize}" Fill="{TemplateBinding Background}"/> <TextBlock Text="{Binding MyText}" HorizontalAlignment="Center" VerticalAlignment="Center"/> </Grid> </ControlTemplate> </Setter.Value> </Setter> </Style> <Style TargetType="{x:Type local:GroupThumb}" BasedOn="{StaticResource kiso}"> <Setter Property="Template"> <Setter.Value> <ControlTemplate> <ItemsControl x:Name="PART_ItemsControl" ItemsSource="{Binding MyThumbs}" Background="{TemplateBinding Background}"> <ItemsControl.ItemsPanel> <ItemsPanelTemplate> <local:ExCanvas/> </ItemsPanelTemplate> </ItemsControl.ItemsPanel> <!--<ItemsControl.ItemContainerStyle> <Style TargetType="Thumb"> <Setter Property="Canvas.Left" Value="{Binding MyLeft}"/> <Setter Property="Canvas.Top" Value="{Binding MyTop}"/> </Style> </ItemsControl.ItemContainerStyle>--> </ItemsControl> </ControlTemplate> </Setter.Value> </Setter> </Style> </ResourceDictionary>
ã«ã¹ã¿ã ã³ã³ããã¼ã«ç¨
ååã«ã使ã£ãBasedOnãæ´»èºãã¦ãã
åè¦ç´ ãæã¤ãã¨ãã§ããGroupThumbã®é¨åã¯ä»¥åã®
WPFãItemsControlã使ã£ã¦è¦ç´ ãå
¥ãåã«ã§ããã«ã¹ã¿ã ã³ã³ããã¼ã«Thumbãã°ã«ã¼ãåã¿ãããªãã® - åå¾ãã¦ãã®ããã°
gogowaten.hatenablog.com
ããããã®ã³ããæ¹å¤
ä»åã¯åè¦ç´ ã並ã¹ãPanelã«ã¯èªåãªãµã¤ãºã®ExCanvasã使ã£ã¦ããã®ã§ãåè¦ç´ ã®ä½ç½®ã¨ãµã¤ãºã«ãã£ã¦ExCanvasã®ãµã¤ãºãå¤åãã¦ãExCanvasã®ãµã¤ãºã¨GroupThumbã®ãµã¤ãºãBindingãã¦ããã®ã§GroupThumbã®ãµã¤ãºãèªåã§å¤åãã
ã³ã¡ã³ãã¢ã¦ãã«ãªã£ã¦ããã¨ãã
åè¦ç´ ã®ä½ç½®ããããã£ãCanvasã®æ·»ä»ããããã£ã®Leftã¨Topã«Bindingããå¿
è¦ãããã¨æã£ã¦æ¸ãããã ãã©ãç¡ãã¦ãåããã®ã§ã³ã¡ã³ãã¢ã¦ããããããã¯ãã¶ãKisoThumbã®Styleã§Bindingãã¦ããã®ããBasedOnã§ç¶æ¿ãã¦ããããï¼
MainWindow.xaml
<Window x:Class="_20241212_ReLayoutGroupThumb.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:_20241212_ReLayoutGroupThumb" mc:Ignorable="d" Title="MainWindow" Height="350" Width="500"> <Grid> <Grid.ColumnDefinitions> <ColumnDefinition/> <ColumnDefinition Width="140"/> </Grid.ColumnDefinitions> <Canvas> <Canvas.Resources> <Style TargetType="{x:Type local:KisoThumb}" x:Key="kiso"> <EventSetter Event="DragCompleted" Handler="KisoThumb_DragCompleted"/> <EventSetter Event="DragDelta" Handler="Thumb_DragDelta"/> </Style> <Style TargetType="{x:Type local:TextBlockThumb}" BasedOn="{StaticResource kiso}"/> <Style TargetType="{x:Type local:EllipseTextThumb}" BasedOn="{StaticResource kiso}"/> <Style TargetType="{x:Type local:GroupThumb}" BasedOn="{StaticResource kiso}"/> </Canvas.Resources> <local:GroupThumb x:Name="MyRootGroup" Background="Lavender"> <local:TextBlockThumb MyText="Group0-1" Background="LightSteelBlue"/> <local:GroupThumb x:Name="MyGroup1" MyLeft="20" MyTop="20" Background="Salmon"> <local:TextBlockThumb x:Name="MyItem1_1" MyLeft="0" MyTop="0" MyText="Group1-1" Background="LightSalmon"/> <local:EllipseTextThumb MyLeft="50" MyTop="50" MySize="60" MyText="Group1-2" Background="Gold"/> </local:GroupThumb> <local:GroupThumb x:Name="MyGroup2" MyLeft="100" MyTop="140" Background="Violet"> <local:TextBlockThumb x:Name="MyItem2_1" MyLeft="0" MyTop="0" MyText="Group2-1" Background="Pink"/> <local:EllipseTextThumb MyLeft="50" MyTop="30" MySize="60" MyText="Group2-2" Background="Gold"/> </local:GroupThumb> </local:GroupThumb> </Canvas> <StackPanel Grid.Column="1"> <Button x:Name="MyButtonText" Content="Group1-1ãå·¦ã«100移å" Click="MyButtonText_Click"/> <GroupBox Header="Group0" Background="{Binding Background}" DataContext="{Binding ElementName=MyRootGroup}"> <StackPanel> <TextBlock Text="{Binding Path=MyLeft, StringFormat={}{0:0.0} Left}"/> <TextBlock Text="{Binding Path=MyTop, StringFormat={}{0:0.0} Top}"/> <TextBlock Text="{Binding Path=ActualWidth, StringFormat={}{0:0.0} width}"/> <TextBlock Text="{Binding Path=ActualHeight, StringFormat={}{0:0.0} height}"/> </StackPanel> </GroupBox> <GroupBox Header="Group1" Background="{Binding Background}" DataContext="{Binding ElementName=MyGroup1}"> <StackPanel> <TextBlock Text="{Binding Path=MyLeft, StringFormat={}{0:0.0} Left}"/> <TextBlock Text="{Binding Path=MyTop, StringFormat={}{0:0.0} Top}"/> <TextBlock Text="{Binding Path=ActualWidth, StringFormat={}{0:0.0} width}"/> <TextBlock Text="{Binding Path=ActualHeight, StringFormat={}{0:0.0} height}"/> </StackPanel> </GroupBox> <GroupBox Header="Group2" Background="{Binding Background}" DataContext="{Binding ElementName=MyGroup2}"> <StackPanel> <TextBlock Text="{Binding Path=MyLeft, StringFormat={}{0:0.0} Left}"/> <TextBlock Text="{Binding Path=MyTop, StringFormat={}{0:0.0} Top}"/> <TextBlock Text="{Binding Path=ActualWidth, StringFormat={}{0:0.0} width}"/> <TextBlock Text="{Binding Path=ActualHeight, StringFormat={}{0:0.0} height}"/> </StackPanel> </GroupBox> </StackPanel> </Grid> </Window>
å¾åã®StackPanelã®ä¸èº«ã¯ãä½ç½®ã¨ãµã¤ãºã®ç¢ºèªç¨ãªã®ã§å¿
è¦ãªã
MainWindow.xaml.cs
using System.Windows; using System.Windows.Controls.Primitives; namespace _20241212_ReLayoutGroupThumb { public partial class MainWindow : Window { public MainWindow() { InitializeComponent(); } private void Thumb_DragDelta(object sender, DragDeltaEventArgs e) { if (sender is KisoThumb t) { t.MyLeft += e.HorizontalChange; t.MyTop += e.VerticalChange; e.Handled = true; } } private void MyButtonText_Click(object sender, RoutedEventArgs e) { MyItem1_1.MyLeft -= 100; MyItem1_1.MyParentThumb?.ReLayout(); } //ãã©ãã°ç§»åçµäºæ private void KisoThumb_DragCompleted(object sender, DragCompletedEventArgs e) { //親è¦ç´ ã®åé ç½® if (sender is KisoThumb t && t.MyParentThumb is not null) { t.MyParentThumb.ReLayout(); } //ã¤ãã³ããããã§åæ¢ e.Handled = true; } } }
ãã©ãã°ç§»åçµäºæã¤ãã³ãã®KisoThumb_DragCompletedã§ã親è¦ç´ ã®ä½ç½®å¤æ´ãå®è¡ãã¦ãã
ãã©ãã°ç§»åãã¦ããã®ã¯åè¦ç´ ã ãã©ãä½ç½®å¤æ´å¦çã¯ãã®è¦ªè¦ç´ ãªã®ã§ãåè¦ç´ ã¯è¦ªè¦ç´ ãç¥ã£ã¦ããå¿
è¦ããã£ãã®ãããããã®ããã®MyParentThumbã ã£ããã
ä»æã£ããã©è¦ªè¦ç´ ã®å(ã¯ã©ã¹)ã¯å¿
ãGroupThumbãªãã ãããMyParentGroupã£ã¦ååã®ã»ããè¯ãã£ããã
ãã¿ã³ã®ã¯ãªãã¯ã¤ãã³ãMyButtonText_Click
ããã§ã¯åè¦ç´ ã®ã®å·¦ä½ç½®ã®å¤ã-100ããå¾ã«ã親è¦ç´ ä½ç½®ã®å¤æ´ããã¦ãã
ææ³
æã£ã¦ãããã ãã¶ç°¡æ½ã«æ¸ãã
2å¹´åã®ã¨ãã¯ãµã¤ãºã¨ä½ç½®ã®èª¿æ´ã§ããªãé£èªãã¦ãã§ããã«ã¯ã§ãããã©ãã¡ããã¡ãã ã£ã
ä»åã®ãããªã©ã¤ã³ããã¸ã§æ²ç·ãå
¥ã£ã¦ããã¨é£ãããªãã®ããï¼
é¢é£è¨äº
æ¨æ¥
WPFãStyleã®å¼ãç¶ã(ç¶æ¿)ãããBasedOnãCustomControlã§ã使ã£ã¦ã¿ã - åå¾ãã¦ãã®ããã°
gogowaten.hatenablog.com
3æ¥å
WPFãèªåãµã¤ãºCanvasãGroupThumbã«ä½¿ã£ã¦ã¿ã - åå¾ãã¦ãã®ããã°
gogowaten.hatenablog.com
4æ¥å
WPFãItemsControlã使ã£ã¦è¦ç´ ãå
¥ãåã«ã§ããã«ã¹ã¿ã ã³ã³ããã¼ã«Thumbãã°ã«ã¼ãåã¿ãããªãã® - åå¾ãã¦ãã®ããã°
gogowaten.hatenablog.com
1é±éå
WPFãã«ã¹ã¿ã ã³ã³ããã¼ã«ä½¿ã£ã¦ã¿ãããã¦ã¹ãã©ãã°ç§»åã§ããTextBlockãä½æ - åå¾ãã¦ãã®ããã°
gogowaten.hatenablog.com