[WPF]背景の境界線の太さの変更が反映されない
Q&A
Closed
お世話になっております。
解決したいこと
C# + WPFでベクターグラフィックスドローイングツール"boiler's Graphics"を開発しています。ライブラリはPrism 8.1.97やReactiveProperty 7.11.0、OpenCvSharp4 4.5.2.20210404等を使用させてもらっています。
発生している問題・エラー
レイヤー機能を実装完了して、バージョン3.0のリリースを前にバグを取り除く作業をしています。それで、取り除こうとしているバグが上手く取り除けないのでQiitaに質問を書かせてもらっています。
当アプリケーションには編集したベクターグラフィックスをJpegやPngファイルに書き出す機能があります。これをエクスポート機能と呼んでいます。
このバグはエクスポートする時に、背景オブジェクトの境界線の太さを0にして画像に出力する処理をするようにコードを書いたのですが、境界線の太さが初期値(アプリケーション起動時に10を設定しましたが、10のまま)の状態で出力されてしまい、エクスポートされた画像ファイルに背景オブジェクトの境界線が出力されてしまいます。
背景オブジェクトの境界線はエクスポートする瞬間は消えてほしいのですが、編集時は見えるようにしておきたいです。
どうしたら望む結果が得られるでしょうか?
ちなみに、エクスポートする瞬間の、ファイル出力結果には境界線(太さ=10)が含まれてしまっていますが、エクスポート完了後は境界線は太さ=1になります。ReactivePropertyによるデータコンテキストの変更通知は上手くいっているようですが、Viewへの反映が上手くいっていないようです。
public class DiagramViewModel : BindableBase, IDiagramViewModel, IDisposable
{
public DiagramViewModel(int width, int height)
{
:
InitialSetting();
}
private void InitialSetting()
{
EdgeColors.Add(Colors.Black);
FillColors.Add(Colors.White);
EdgeThickness.Value = 1.0;
CanvasBorderThickness = 0.0;
CanvasBackground.Value = Colors.White;
//背景オブジェクトの登録
BackgroundItem.Value = new BackgroundViewModel();
BackgroundItem.Value.ZIndex.Value = -1;
BackgroundItem.Value.FillColor.Value = CanvasBackground.Value;
BackgroundItem.Value.Left.Value = 0;
BackgroundItem.Value.Top.Value = 0;
BackgroundItem.Value.Width.Value = Width;
BackgroundItem.Value.Height.Value = Height;
BackgroundItem.Value.Owner = this;
BackgroundItem.Value.EdgeColor.Value = Colors.Black;
BackgroundItem.Value.EdgeThickness.Value = 10; //バグ視認しやすさ重視で太くする
BackgroundItem.Value.EnableForSelection.Value = false;
BackgroundItem.Value.IsVisible.Value = true;
EnablePointSnap.Value = true;
Layer.LayerCount = 1;
LayerItem.LayerItemCount = 1;
var layer = new Layer();
layer.IsVisible.Value = true;
layer.IsSelected.Value = true;
layer.Name.Value = Name.GetNewLayerName();
Random rand = new Random();
layer.Color.Value = Randomizer.RandomColor(rand);
Layers.Add(layer);
}
:
}
class ExportViewModel : BindableBase, IDialogAware, IDisposable
{
:
public ReactiveProperty<string> Path { get; set; } = new ReactiveProperty<string>();
public ReactiveCommand ExportCommand { get; set; }
:
public ExportViewModel()
{
:
ExportCommand = Path
.Where(x => x != null)
.Select(x => x.Length > 0)
.ToReactiveCommand();
ExportCommand.Subscribe(_ => ExportProcess())
.AddTo(_disposables);
:
Path.Value = "";
}
private void ExportProcess()
{
Dictionary<SelectableDesignerItemViewModelBase, bool> tempIsSelected;
BeforeExport(out tempIsSelected);
Export();
AfterExport(tempIsSelected);
OkClose();
}
private void BeforeExport(out Dictionary<SelectableDesignerItemViewModelBase, bool> tempIsSelected)
{
var diagramControl = App.Current.MainWindow.GetChildOfType<DiagramControl>();
var itemsControl = diagramControl.GetChildOfType<ItemsControl>();
tempIsSelected = new Dictionary<SelectableDesignerItemViewModelBase, bool>();
var diagramViewModel = diagramControl.DataContext as DiagramViewModel;
var backgroundItem = diagramViewModel.BackgroundItem.Value;
foreach (var item in itemsControl.Items.Cast<SelectableDesignerItemViewModelBase>())
{
tempIsSelected.Add(item, item.IsSelected.Value);
//一時的に選択状態を解除する
item.IsSelected.Value = false;
}
foreach (var snapPointVM in itemsControl.Items.OfType<SnapPointViewModel>())
{
//一時的に非表示にする
snapPointVM.Opacity.Value = 0;
}
//一時的に背景の境界線を消す
backgroundItem.EdgeThickness.Value = 0; //not working
}
private void Export()
{
var diagramControl = App.Current.MainWindow.GetChildOfType<DiagramControl>();
var itemsControl = diagramControl.GetChildOfType<ItemsControl>();
var designerCanvas = diagramControl.GetChildOfType<DesignerCanvas>();
var diagramViewModel = diagramControl.DataContext as DiagramViewModel;
var backgroundItem = diagramViewModel.BackgroundItem.Value;
var rtb = new RenderTargetBitmap((int)diagramViewModel.Width, (int)diagramViewModel.Height, 96, 96, PixelFormats.Pbgra32);
DrawingVisual visual = new DrawingVisual();
using (DrawingContext context = visual.RenderOpen())
{
//背景を描画
RenderDesignerItemViewModelBase(context, backgroundItem); //※1 backgroundItem.EdgeThicknessはこの時点では0になっている、加えて当メソッド内のviewのStrokeThicknessも0になっている
foreach (var item in diagramViewModel.AllItems.Value.Except(new SelectableDesignerItemViewModelBase[] { backgroundItem }).OrderBy(x => x.ZIndex.Value))
{
if (item is DesignerItemViewModelBase designerItem)
{
RenderDesignerItemViewModelBase(context, designerItem);
}
else if (item is ConnectorBaseViewModel connector)
{
RenderConnectorBaseViewModel(context, connector);
}
}
}
rtb.Render(visual);
OpenCvSharpHelper.ImShow("test", rtb); //デバッグ用RenderTargetBitmap->OpenCvSharp.Matに変換してCv2.ImShowする
var generator = FileGenerator.Create(System.IO.Path.GetExtension(Path.Value));
generator.AddFrame(BitmapFrame.Create(rtb));
generator.SetQualityLevel(QualityLevel.Value);
generator.Save(Path.Value);
}
private void AfterExport(Dictionary<SelectableDesignerItemViewModelBase, bool> tempIsSelected)
{
var diagramControl = App.Current.MainWindow.GetChildOfType<DiagramControl>();
var itemsControl = diagramControl.GetChildOfType<ItemsControl>();
var diagramViewModel = diagramControl.DataContext as DiagramViewModel;
var backgroundItem = diagramViewModel.BackgroundItem.Value;
//背景の境界線を復元する
backgroundItem.EdgeThickness.Value = 1;
//IsSelectedの復元
foreach (var item in itemsControl.Items.Cast<SelectableDesignerItemViewModelBase>())
{
bool outIsSelected;
if (tempIsSelected.TryGetValue(item, out outIsSelected))
{
item.IsSelected.Value = outIsSelected;
}
}
foreach (var snapPointVM in itemsControl.Items.OfType<SnapPointViewModel>())
{
//スナップポイントを半透明に復元する
snapPointVM.Opacity.Value = 0.5;
}
}
private static void RenderConnectorBaseViewModel(DrawingContext context, ConnectorBaseViewModel connector)
{
var diagramControl = App.Current.MainWindow.GetChildOfType<DiagramControl>();
var view = diagramControl.GetCorrespondingViews<FrameworkElement>(connector).First(x => x.GetType() == connector.GetViewType());
VisualBrush brush = new VisualBrush(view);
context.DrawRectangle(brush, null, new Rect(connector.LeftTop.Value, new Size(connector.Width.Value, connector.Height.Value)));
}
private static void RenderDesignerItemViewModelBase(DrawingContext context, DesignerItemViewModelBase designerItem)
{
var diagramControl = App.Current.MainWindow.GetChildOfType<DiagramControl>();
var view = diagramControl.GetCorrespondingViews<FrameworkElement>(designerItem).First(x => x.GetType() == designerItem.GetViewType());
VisualBrush brush = new VisualBrush(view);
context.DrawRectangle(brush, null, new Rect(new Point(designerItem.Left.Value, designerItem.Top.Value), new Size(designerItem.Width.Value, designerItem.Height.Value)));
}
:
}
背景オブジェクトはBackgroundViewModelクラスのインスタンスで、BackgroundDataTemplate.xamlに定義されているDataTemplateが適用されます。
<ResourceDictionary
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:convereter="clr-namespace:boilersGraphics.Converters"
xmlns:local="clr-namespace:boilersGraphics.Resources.DesignerItems"
xmlns:viewModel="clr-namespace:boilersGraphics.ViewModels">
<convereter:ToSolidColorBrushConverter x:Key="solidColorBrushConverter" />
<DataTemplate DataType="{x:Type viewModel:BackgroundViewModel}">
<Path Stroke="{Binding EdgeColor.Value, Converter={StaticResource solidColorBrushConverter}}"
StrokeThickness="{Binding EdgeThickness.Value}"
IsHitTestVisible="False"
Stretch="Fill"
Fill="{Binding FillColor.Value, Converter={StaticResource solidColorBrushConverter}}"
Data="{Binding PathGeometry.Value}" />
</DataTemplate>
</ResourceDictionary>
掲載したソースコードの場所一覧
- ViewModels/DiagramViewModel.cs
- ViewModels/ExportViewModel.cs
- Resources/DesignerItems/BackgroundDataTemplate.xaml
ソースコード
boiler's Graphics
https://github.com/dhq-boiler/boiler-s-Graphics
gitリポジトリ
https://github.com/dhq-boiler/boiler-s-Graphics.git
ブランチ:develop
コミット:bb59418
何か私の見落とし、致命的な勘違いなど気づいたところがあれば、回答していただけると助かります。当プロジェクトのソースコードは意外にも巨大なので、検証に苦労するかもしれませんが、よろしくお願いいたします。
当アプリの使い方
当アプリケーションboiler's Graphicsの使い方が知りたいという方のために、使い方紹介ページを用意しました。是非参考にしてください。→HowToUse