Strokeを半透明にしたときに、塗りつぶしと重ならないようにするよ。
こんにちは!
冬が近づき空気が乾燥していくなかで、リップクリームで唇の荒れをふさぎつつ、 このへんは進化の過程でどうにかならなかったんだろうか、、と嘆いているあらたまです!
今日は WPFアプリケーションの図形の枠線のお話です。
Strokeで外枠線を引いてみる
まずはベーシックな方法で、StrokeとStrokeThicknessを使って外枠線を引いてみましょう。
今回はサンプルとして円形の図形を使用しました。
<Window x:Class="BlogEntry84.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:BlogEntry84"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<StackPanel Orientation="Horizontal" Margin="15">
<Ellipse Fill="Green"
Stroke="Orange"
StrokeThickness="25"
Height="243"
Width="243"
>
</Ellipse>
</StackPanel>
</Window>
通常はこれで問題ないのですが、外枠を半透明にしたい要望があった場合に問題が生じます。
Strokeを半透明にしてみる
では、試しに外枠を半透明にしてみましょう。
<Window x:Class="BlogEntry84.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:BlogEntry84"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<StackPanel Orientation="Horizontal" Margin="15">
<Ellipse Fill="Green"
StrokeThickness="25"
Height="243"
Width="243"
>
<Ellipse.Stroke>
<SolidColorBrush Color="Orange" Opacity="0.5" />
</Ellipse.Stroke>
</Ellipse>
</StackPanel>
</Window>
オレンジ色の枠線と、緑色の塗りつぶしが重なってしまっています。。
この緑色の塗りつぶしを、枠線と重ならないようにする方法です。
ソースコード
まずは対処後のソースコードをお見せしますね。
FrameworkElementの派生クラス
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Media;
using System.Windows.Shapes;
namespace BlogEntry84
{
class OutlinedEllipse : FrameworkElement
{
public static readonly DependencyProperty FillProperty = DependencyProperty.Register(
"Fill",
typeof(Brush),
typeof(OutlinedEllipse),
new FrameworkPropertyMetadata(Brushes.Black, FrameworkPropertyMetadataOptions.AffectsRender));
public static readonly DependencyProperty StrokeProperty = DependencyProperty.Register(
"Stroke",
typeof(Brush),
typeof(OutlinedEllipse),
new FrameworkPropertyMetadata(Brushes.Black, FrameworkPropertyMetadataOptions.AffectsRender));
public static readonly DependencyProperty StrokeThicknessProperty = DependencyProperty.Register(
"StrokeThickness",
typeof(double),
typeof(OutlinedEllipse),
new FrameworkPropertyMetadata(1d, FrameworkPropertyMetadataOptions.AffectsRender));
/// <summary>
/// 塗りつぶしブラシ
/// </summary>
public Brush Fill
{
get { return (Brush)GetValue(FillProperty); }
set { SetValue(FillProperty, value); }
}
/// <summary>
/// 外枠線ブラシ
/// </summary>
public Brush Stroke
{
get { return (Brush)GetValue(StrokeProperty); }
set { SetValue(StrokeProperty, value); }
}
/// <summary>
/// 外枠線の太さ
/// </summary>
public double StrokeThickness
{
get { return (double)GetValue(StrokeThicknessProperty); }
set { SetValue(StrokeThicknessProperty, value); }
}
/**
* OutlinedEllipse描画時の処理
**/
protected override void OnRender(DrawingContext drawingContext)
{
// 外側円のGeometory取得
Geometry bigGeometry = getGeometry(0);
// 内側円のGeometory取得
Geometry smallGeometry = getGeometry(this.StrokeThickness);
// 枠線分のGeometry
var strokeGeometry = Geometry.Combine(bigGeometry, smallGeometry, GeometryCombineMode.Exclude, null);
// 外枠描画
drawingContext.DrawGeometry(this.Stroke, null, strokeGeometry);
// 塗りつぶし描画
drawingContext.DrawGeometry(this.Fill, null, smallGeometry);
}
/// <summary>
/// Geometry取得
/// </summary>
private Geometry getGeometry(double strokeThickness)
{
double width = this.ActualWidth - strokeThickness * 2;
double height = this.ActualHeight - strokeThickness * 2;
height = height < 0 ? 0 : height;
width = width < 0 ? 0 : width;
Shape shape = new Ellipse()
{
Height = height,
Width = width
};
shape.Measure(new Size(width, height));
shape.Arrange(new Rect(0, 0, width, height));
Geometry geometry = shape.RenderedGeometry.Clone();
geometry.Transform = new TranslateTransform(strokeThickness, strokeThickness);
return geometry;
}
}
}
XAML
<Window x:Class="BlogEntry84.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:BlogEntry84"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<StackPanel Orientation="Horizontal" Margin="15">
<Ellipse Fill="Green"
StrokeThickness="25"
Height="243"
Width="243"
Margin="0,0,25,0"
>
<Ellipse.Stroke>
<SolidColorBrush Color="Orange" Opacity="0.5" />
</Ellipse.Stroke>
</Ellipse>
<local:OutlinedEllipse Fill="Green"
StrokeThickness="25"
Height="243"
Width="243">
<local:OutlinedEllipse.Stroke>
<SolidColorBrush Color="Orange" Opacity="0.5" />
</local:OutlinedEllipse.Stroke>
</local:OutlinedEllipse>
</StackPanel>
</Window>
実行結果は下の画像のようになります。
左側が通常のStrokeを使用した場合、右側が対処後です。
枠線と塗りつぶしが重ならないようになりました。
簡単な説明
【OutlinedEllipseクラス】
FrameworkElementの派生クラスとして作成しています。
描画時に必要な、塗りつぶし色、外枠線色、外枠線太さを依存プロパティとしてXAMLより受け取られるようにしています。
自身の描画時(OnRenderメソッド)で、外側円のGeometry、内側円のGeometryを作成して その差分を取ることで外枠線のGeometryとしています。
あとは、内側線のGeometryを塗りつぶし色で描画、外枠線のGeometryを外枠線で描画するだけです。
【XAML】
OutlinedEllipseクラスに塗りつぶし色、外枠線色、外枠線太さ、高さ、幅を与えて呼び出しています。
高さ、幅については、OutlinedEllipseクラスの基底クラスであるFrameworkElementクラスのものを使用しています。
色々な図形への対応
今回は円というシンプルな図形のため、OutlinedEllipseクラスのgetGeometryメソッドがとてもシンプルになっています。
ここでのwidth, height, TranslateTransformの生成のしかたを工夫することで、三角形や矢印などの様々な図形にも対応可能です!