WPF、マウスでTextBoxのサイズ変更するのにAdorner(装飾者)を使ってみた
サイズ変更のつまみは右下だけの簡易なもので、実質50行
結果
回転表示させているTextBoxのサイズ変更も違和感なくできているのがAdornerのすごいところだと思った
もしAdornerを使わずにCanvas上にThumbを配置っていう以前の方法だと回転角度の分がずれるから、その修正処理が必要だったはず
環境
- Windows 10 Home バージョン 21H2
- Visual Studio Community 2022 Version 17.5.1
- WPF
- C#
- .NET 6.0
コード
2023WPF/20230305_Adorner/20230305_Adorner at main · gogowaten/2023WPF
EzAdorner.cs
using System; using System.Windows.Controls.Primitives; using System.Windows; using System.Windows.Documents; using System.Windows.Input; using System.Windows.Media; //embellishとadornとdecorateの語感の違い... - Yahoo!知恵袋 //https://detail.chiebukuro.yahoo.co.jp/qa/question_detail/q1023045950 //adorn:飾る、装飾する、アドーン //adorner:装飾者、アドーナー、アドナー //装飾:decorate、装飾者:decorator //装飾の概要 - WPF .NET Framework | Microsoft Learn //https://learn.microsoft.com/ja-jp/dotnet/desktop/wpf/controls/adorners-overview?view=netframeworkdesktop-4.8 //WPF Adorners Part 1 – What are adorners //https://www.nbdtech.com/Blog/archive/2010/06/21/wpf-adorners-part-1-ndash-what-are-adorners.aspx //Mitesh Sureja's Blog: Adorners in WPF // http://miteshsureja.blogspot.com/2016/08/adorners-in-wpf.html //簡略化した、例外とか考えてない、Thumbは右下に一個だけ //装飾できるのはUIElementから狭めてFrameworkElementに変更 namespace _20230305_Adorner { public class EzAdoner : Adorner { readonly Thumb MyThumb;//サイズ変更用つまみ readonly VisualCollection MyVisualChildren;//表示したい要素を管理する用? readonly FrameworkElement MyTarget;//装飾する対象要素 public EzAdoner(FrameworkElement adornedElement) : base(adornedElement) { MyTarget = adornedElement; MyVisualChildren = new VisualCollection(this); MyThumb = new Thumb() { Cursor = Cursors.SizeNWSE, Height = 20, Width = 20, Opacity = 0.5, Background = Brushes.Red, }; MyThumb.DragDelta += MyThumb_DragDelta; MyVisualChildren.Add(MyThumb); //TextBoxなどWidthの既定値がNaNなのを解除する MyTarget.Width = MyTarget.DesiredSize.Width; MyTarget.Height = MyTarget.DesiredSize.Height; } private void MyThumb_DragDelta(object sender, DragDeltaEventArgs e) { //対象要素のサイズ変更、Thumbより小さくならないようにする MyTarget.Width = Math.Max(MyTarget.Width + e.HorizontalChange, MyThumb.Width); MyTarget.Height = Math.Max(MyTarget.Height + e.VerticalChange, MyThumb.Height); } protected override Size ArrangeOverride(Size finalSize) { //Thumbの表示位置修正、常に対象要素の右下に表示 MyThumb.Arrange(new Rect( MyTarget.Width / 2, MyTarget.Height / 2, MyTarget.Width, MyTarget.Height)); return base.ArrangeOverride(finalSize); } #region VisualCollectionで必要 protected override int VisualChildrenCount => MyVisualChildren.Count; protected override Visual GetVisualChild(int index) => MyVisualChildren[index]; #endregion VisualCollectionで必要 } }
参照したところは
Mitesh Sureja's Blog: Adorners in WPF
ここのを元にして、右下Thumbだけに絞って、その他の要らなさそうなのを削りまくったので、なんか不具合あるかも
MainWindow.xaml
<Window x:Class="_20230305_Adorner.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:_20230305_Adorner" mc:Ignorable="d" Title="MainWindow" Height="350" Width="400"> <Grid> <Canvas> <Ellipse x:Name="MyEllipse" Fill="Lavender" Stroke="DarkMagenta" Canvas.Left="20" Canvas.Top="100" Width="50" Height="50"/> <TextBox x:Name="MyTextBox" Text="TextBox" FontSize="30" Canvas.Left="100" Canvas.Top="20"> <TextBox.RenderTransform> <RotateTransform Angle="20"/> </TextBox.RenderTransform> </TextBox> </Canvas> </Grid> </Window>
MainWindow.xaml.cs
using System.Windows; using System.Windows.Documents; namespace _20230305_Adorner { public partial class MainWindow : Window { public MainWindow() { InitializeComponent(); //Loaded時に装飾する Loaded += MainWindow_Loaded; } private void MainWindow_Loaded(object sender, RoutedEventArgs e) { //静的メソッドのGetAdornerLayerで対象要素のAdornerLayerを取得して //そこにAdornerを追加する if (AdornerLayer.GetAdornerLayer(MyTextBox) is AdornerLayer layer) { layer.Add(new EzAdoner(MyTextBox)); layer.Add(new EzAdoner(MyEllipse)); } //if(AdornerLayer.GetAdornerLayer(MyEllipse)is AdornerLayer layer1) //{ // layer1.Add(new EzAdoner(MyEllipse)); //} } } }
よくわからんところ
VisualCollection
これは検索しても日本語だとあんまり出てこない
このなかにThumbを入れて管理しているみたい
VisualChildrenCountとGetVisualChildのオーバーライドが必要みたいで、それぞれThumbの個数と、指定IndexのThumbを返さないとThumbが表示されない
基準座標(0, 0)は対象要素の中央?
普通は座標(0, 0)は左上なんだけど、違うみたいで
Thumbの表示座標とサイズを指定しているみたいなところ、ArrangeOverrideのなかでのThumb.Arrangeの部分
左上が(0, 0)とするなら右下座標は対象要素の(幅, 高さ)になるはずなんだけど、実際には幅、高さともに半分の位置を指定している。ってことは対象要素の中央が(0, 0)?
今思ったけどこれはAdornerなのか、VisualCollectionどっち?
感想
Adornerは装飾対象と一体化しているような分離されているような不思議な感じがする
マウスでの要素サイズ変更はThumbとAdornerを使うのが正攻法っぽいのは検索するとわかるんだけど、難しそうで避けてきた
それでも今までの方法ではめんどくさいことが出てきたので、Adornerならどうなんだろうってことで少し試してみたのが今回で、4回目くらいかなあ
継承とかオーバーライドとか難しいねん
発覚しためんどくさいことは、作っているPixtack紫陽花3での矢印ベジェ曲線
矢印と頂点移動用のThumbを一体化しているから、矢印ベジェ曲線だけのサイズを取得するのがめんどくさいことがわかった
今取得できているサイズが灰色の部分
これが直線Polylineだとすべての頂点は線上にあるから問題なかったけど、ベジェ曲線だと制御点が線上にない場合が多いからサイズもそうだし座標もどうすればいいのかわからん
Adornerを使ってうまくできればいいなあってところ
Visual Studioのフォントの配色
Visual Studioの背景を黒にしてフォントの配色も変更してみた
Visual Studioにもフォントの配色の保存とかエクスポートとかあればいいのにねえ、あればファイル名はDFIのLANPARTY
こっちはAOpenかなあ
参照したところ
embellishとadornとdecorateの語感の違い - Yahoo!知恵袋
detail.chiebukuro.yahoo.co.jp
装飾の概要 - WPF .NET Framework | Microsoft Learn
learn.microsoft.com
WPF Adorners Part 1 – What are adorners
www.nbdtech.com
簡潔
Mitesh Sureja's Blog: Adorners in WPF
miteshsureja.blogspot.com
関連記事
前回のWPF記事は一昨日
gogowaten.hatenablog.com
以前のサイズ変更方法、271日前
gogowaten.hatenablog.com
268日前
gogowaten.hatenablog.com