WPF、Rectangleとかに2色の破線(点線)枠表示
- 結果
- コード
- 0.ぼやけるの禁止
- 1.XAML+Rectangleそのまま
- 2.XAML+VisualBrush
- 3.1番をC#で
- 4.2番をC#で
- 5.ImageBrush
- 問題
- 破線が1色じゃあかんの?
- 関連記事
結果
サイズ100x50のRectangleに破線(点線)枠表示
破線パターンの色は赤と白の2色、これを1ピクセルごと交互に配置
これを5種類の方法で表示したところ
1~4は全く同じ結果、5は微妙に違う
コード
XAML(デザイン画面)
<Window x:Class="_20220528_2ColorDashStroke.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:_20220528_2ColorDashStroke" mc:Ignorable="d" Title="MainWindow" Height="250" Width="300"> <Canvas x:Name="MyCanvas" Background="Black" UseLayoutRounding="True" RenderOptions.EdgeMode="Aliased"> <Grid Canvas.Left="10" Canvas.Top="10"> <Rectangle Stroke="Red" Width="100" Height="50"/> <Rectangle Stroke="White" StrokeDashArray="1.0"/> </Grid> <Rectangle Width="100" Height="50" Canvas.Left="10" Canvas.Top="70"> <Rectangle.Stroke> <VisualBrush Stretch="None"> <VisualBrush.Visual> <Grid RenderOptions.EdgeMode="Aliased"> <Rectangle Stroke="Red" Width="100" Height="50"/> <Rectangle Stroke="White" StrokeDashArray="1.0"/> </Grid> </VisualBrush.Visual> </VisualBrush> </Rectangle.Stroke> </Rectangle> </Canvas> </Window>
MainWindow.xaml.cs
using System.Windows; using System.Windows.Controls; using System.Windows.Media; using System.Windows.Media.Imaging; using System.Windows.Shapes; //2色の破線枠のテスト namespace _20220528_2ColorDashStroke { public partial class MainWindow : Window { public MainWindow() { InitializeComponent(); Test2VisualBrush(); Test1RectangleRectangle(); Test3ImageBrush(); } //Rectangleを2つをそのまま使う private void Test1RectangleRectangle() { Rectangle r1 = new() { Width = 100, Height = 50, Stroke = Brushes.Red }; Rectangle r2 = new() { Width = 100, Height = 50, Stroke = Brushes.White, StrokeDashArray = new DoubleCollection() { 1.0 } }; Grid g = new(); RenderOptions.SetEdgeMode(g, EdgeMode.Aliased); Canvas.SetLeft(g, 120); Canvas.SetTop(g, 10); g.Children.Add(r1); g.Children.Add(r2); MyCanvas.Children.Add(g); } //Rectangleを2つをVisualBrushとして使う private void Test2VisualBrush() { double w = 100; double h = 50; Rectangle br1 = new() { Width = w, Height = h, Stroke = Brushes.Red }; Rectangle br2 = new() { Width = w, Height = h, Stroke = Brushes.White, StrokeDashArray = new DoubleCollection() { 1.0 } }; Grid g = new(); RenderOptions.SetEdgeMode(g, EdgeMode.Aliased); g.Children.Add(br1); g.Children.Add(br2); VisualBrush vb = new(g); Rectangle r = new() { Width = w, Height = h }; Canvas.SetLeft(r, 120); Canvas.SetTop(r, 70); r.Stroke = vb; MyCanvas.Children.Add(r); } //bitmapで破線パターンを作成してImageBrushとして使う private void Test3ImageBrush() { ImageBrush? b = My2ColorDashBrush(); Rectangle r = new() { Width = 100, Height = 50, Stroke = b, StrokeThickness = 1.0 }; Canvas.SetLeft(r, 120); Canvas.SetTop(r, 130); MyCanvas.Children.Add(r); } private ImageBrush My2ColorDashBrush() { WriteableBitmap bitmap = MakeCheckPattern(1, Colors.Red, Colors.White); ImageBrush brush = new(bitmap) { Stretch = Stretch.None, TileMode = TileMode.Tile, Viewport = new Rect(0, 0, bitmap.PixelWidth, bitmap.PixelHeight), ViewportUnits = BrushMappingMode.Absolute }; return brush; } // 無限の透明市松模様をWriteableBitmapとImageBrushのタイル表示で作成 - 午後わてんのブログ //https://gogowaten.hatenablog.com/entry/15917385 /// <summary> /// 指定した2色から市松模様のbitmapを作成 /// </summary> /// <param name="cellSize">1以上を指定、1指定なら2x2ピクセル、2なら4x4ピクセルの画像作成</param> /// <param name="c1"></param> /// <param name="c2"></param> /// <returns></returns> private WriteableBitmap MakeCheckPattern(int cellSize, Color c1, Color c2) { int width = cellSize * 2; int height = cellSize * 2; var wb = new WriteableBitmap(width, height, 96, 96, PixelFormats.Bgra32, null); int stride = wb.Format.BitsPerPixel / 8 * width; byte[] pixels = new byte[stride * height]; Color iro; for (int y = 0; y < height; y++) { for (int x = 0; x < width; x++) { if ((y < cellSize & x < cellSize) | (y >= cellSize & x >= cellSize)) { iro = c1; } else { iro = c2; } int p = y * stride + x * 4; pixels[p] = iro.B; pixels[p + 1] = iro.G; pixels[p + 2] = iro.R; pixels[p + 3] = iro.A; } } wb.WritePixels(new Int32Rect(0, 0, width, height), pixels, stride, 0); return wb; } //未使用 //白と指定色を横に交互に並べた画像、縦1ピクセル private WriteableBitmap MakePattern2(int length, Color color) { int width = length * 2; WriteableBitmap wb = new(width, 1, 96, 96, PixelFormats.Rgb24, null); int stride = wb.Format.BitsPerPixel / 8 * width; byte[] pixels = new byte[stride]; for (int x = 0; x < length; x += 3) { pixels[x] = color.R; pixels[x + 1] = color.G; pixels[x + 2] = color.B; } for (int x = length; x < width; x += 3) { pixels[x] = 255; pixels[x + 1] = 255; pixels[x + 2] = 255; } wb.WritePixels(new Int32Rect(0, 0, width, 1), pixels, stride, 0); return wb; } } }
0.ぼやけるの禁止
10行目
UseLayoutRounding="True"
RenderOptions.EdgeMode="Aliased"
今回も表示はくっきりさせたいので、この2つを指定、重要
1.XAML+Rectangleそのまま
Gridパネルの中に同じサイズのRectangleを2つ重ねているだけ
下のRectangleは赤の実線枠を表示して、上のRectangleは白の破線枠を表示
この方法のいいところは4行で済むので楽
気になるところは、要素を3つも表示するから、他の方法よりもリソース消費が大きいかも?
そうだとしても1000個とか表示しなければ違いはでなさそうなので、この方法が一番いいかも
2.XAML+VisualBrush
1番の方法で表示したものからVisualBrushを作成して、それを枠(Stroke)のブラシに指定する感じ
21行目、
Grid RenderOptions.EdgeMode="Aliased"
これが必須で、指定なしだと
ぼやけて赤と白が混じって表示されて、破線じゃなくなる
一方、19行目の
VisualBrush Stretch="None"
これは指定しなくても問題なかったけど、まあいいや
3.1番をC#で
これは1番の方法をC#で書いただけ
長い
XAMLだと4行で済んでいたのに長い
C#で書くほうが好きだけど、これだけ違うと事情がない限りXAMLで書く
4.2番をC#で
これも2番のXAMLをC#で書いただけ
XAMLでも12行あったから、それほど増えていない感じがする
5.ImageBrush
破線を表現する画像を作成して、それを元にImageBrushを作成、それを枠線(Stroke)に指定
5-1.破線画像作成
これは
無限の透明市松模様をWriteableBitmapとImageBrushのタイル表示で作成
このときのを応用
これに赤と白、サイズ1ピクセルを指定してできあがる画像は
マウスカーソルの先にあるのがそれ
拡大してみると
指定した2色が交互に配置されている
5-2.ImageBrushを作成
破線画像からImageBrushを作成
色々指定しているのはどれもぼやけるのをなくすためけど、必須だったのは74と76行目だけだったかな、それでも引き伸ばされたりアンチエイリアスがかからないように全部指定した
5-.3.StrokeにImageBrushを指定
あとは
これで完了
5番の方法は1~4に比べてかなりめんどくさいけど
1~4までの方法だと角のパターンで同じ色が連続して表示されることがある、5の方法なら期待通り
問題
線を太くしたとき
1番での調整は
5番の調整は
64行目の
Rectangle r = new() { Width = 100, Height = 50, Stroke = b, StrokeThickness = 1.0 };
から
Rectangle r = new() { Width = 100, Height = 50, Stroke = b, StrokeThickness = 10.0 };
70行目の
WriteableBitmap bitmap = MakeCheckPattern(1, Colors.Red, Colors.White);
から
WriteableBitmap bitmap = MakeCheckPattern(10, Colors.Red, Colors.White);に変更
結果
1~4の方法だと角のところがいまいちな表示になる、一方5番は調整すれば期待どおりになる
枠表示する要素のサイズ変更
今までは100x50と切の良い数値のサイズだったのを
101x51にしてみると
1番はさっきと大差ないけど、5番が残念な表示になっている
枠表示する要素のサイズが太さの整数倍以外だとこうなってしまうはずで、これは調整じゃ解決できないかなあ
破線が1色じゃあかんの?
黒背景に白破線、全く問題ない
これが青破線だと
見づらい、さらに黒破線なら全く見えなくなる
背景色が決まっているとかなら問題なけど、そうじゃない場合は2色使ったほうが安定する
白背景になっても
関連記事
次回のWPF記事
gogowaten.hatenablog.com
前回のWPF記事は171日前
gogowaten.hatenablog.com