C#でWPF学習中「Behaviorを使ってDrag&Dropで画像を表示する」

C# コンピュータ
C#
以前Drag&Dropで画像ファイルを開くプログラムを作成した際、Drop部分はEventTriggerを使いましたが、DragOrver部分はコードビハインドでコーディングしました。
今回はDragOver部分をBehaviorでコーディングしてみたいと思います。

また、ReactivePropertyは使い終わったらDispose()を呼んでおいた方が良いとのことですので、WindowのクローズイベントでViewModelをDispose()するビヘイビアを作り、そちらでReactivePropertyなオブジェクトもDispose()するようにしてみました。

実行環境

Windows10 2004
dotnet –version 5.0.104
Visual Studio Code
PowerShell 5.1

プロジェクトの作成

mkdir プロジェクト名
cd プロジェクト名
dotnet new wpf
dotnet add package Microsoft.Xaml.Behaviors.Wpf
dotnet add package ReactiveProperty.WPF
dotnet add package OpenCvSharp4.Windows
code .

ソースコード

ファイル名:MainWindow.xaml

<Window x:Class="WpfSample18DD.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:i="clr-namespace:Microsoft.Xaml.Behaviors;assembly=Microsoft.Xaml.Behaviors"
        xmlns:interactivity="clr-namespace:Reactive.Bindings.Interactivity;assembly=ReactiveProperty.WPF"
        xmlns:local="clr-namespace:WpfSample18DD"
        mc:Ignorable="d"
        Title="{Binding Titlebar.Value}" Height="450" Width="800">
    <Window.DataContext>
        <local:MainWindowViewModel />
    </Window.DataContext>
    <i:Interaction.Behaviors>
        <local:WindowCloseBehavior />
    </i:Interaction.Behaviors>
    <Grid>
        <ScrollViewer
            HorizontalScrollBarVisibility="Visible" 
            VerticalScrollBarVisibility="Visible" 
            Background="Azure">
            <Canvas
                Width="{Binding CanvasWidth.Value}"
                Height="{Binding CanvasHeight.Value}"
                Background="Gray"
                AllowDrop="True">          
                <i:Interaction.Behaviors>
                    <local:DragOverBehavior />
                </i:Interaction.Behaviors>
                <i:Interaction.Triggers>
                    <i:EventTrigger EventName="Drop">
                        <interactivity:EventToReactiveCommand Command="{Binding DropCommand}" />
                    </i:EventTrigger>
                </i:Interaction.Triggers>
                <Image
                    Stretch="None"
                    Source="{Binding PictureView.Value}">
                </Image>
            </Canvas>
        </ScrollViewer>
    </Grid>
</Window>

ファイル名:MainWindowViewModel.cs

using System.Diagnostics;
using Reactive.Bindings;
using System.Reactive.Disposables;
using System.ComponentModel;
using System.Windows.Media.Imaging;
using System.Windows;
using System;
using System.Threading.Tasks;

using OpenCvSharp;
using OpenCvSharp.WpfExtensions;

using System.Collections.Generic;

using System.Windows.Media;

namespace WpfSample18DD
{
    public class MainWindowViewModel : INotifyPropertyChanged, IDisposable
    {
        public event PropertyChangedEventHandler PropertyChanged;
        private CompositeDisposable Disposable { get; } = new CompositeDisposable();

        public ReactiveProperty<string> Titlebar { get; private set; }
        public ReactiveProperty<string> CanvasHeight { get; private set; }
        public ReactiveProperty<string> CanvasWidth { get; private set; }
        public ReactiveProperty<WriteableBitmap> PictureView { get; private set; }
        public ReactiveCommand<DragEventArgs> DropCommand { get; }

        protected virtual void OnPropertyChanged(string name)
        {
            PropertyChanged(this, new PropertyChangedEventArgs(name));
        }

        private WriteableBitmap LoadPicture(string path)
        {
            WriteableBitmap bitmapSource;

            using(var mat = new Mat(path))
            {
                bitmapSource = (WriteableBitmap)BitmapSourceConverter.ToBitmapSource(mat);
                bitmapSource.Freeze();
            }

            return bitmapSource;
        }
        public MainWindowViewModel()
        {
            PropertyChanged += (o, e) => {};

            PictureView = new ReactiveProperty<WriteableBitmap>();
            Disposable.Add(PictureView);

            Titlebar = new ReactiveProperty<string>("MainWindow");
            Disposable.Add(Titlebar);

            CanvasHeight = new ReactiveProperty<string>("AUTO");
            Disposable.Add(CanvasHeight);

            CanvasWidth = new ReactiveProperty<string>("AUTO");
            Disposable.Add(CanvasWidth);

            CanvasHeight.Subscribe(_ => {
                Titlebar.Value = string.Format("W:{0} H:{1}", CanvasWidth.Value, CanvasHeight.Value);
            });
            CanvasWidth.Subscribe(_ => {
                Titlebar.Value = string.Format("W:{0} H:{1}", CanvasWidth.Value, CanvasHeight.Value);
            });

            DropCommand = new ReactiveCommand<DragEventArgs>();
            DropCommand.Subscribe(async e => {
                if (e.Data.GetDataPresent(DataFormats.FileDrop) == false) return;
                var path = ((string[])e.Data.GetData(DataFormats.FileDrop))[0];
                var ext = System.IO.Path.GetExtension(path).ToUpper();
                var validExtensions = new List<string> {".PNG", ".JPEG", ".JPG", ".BMP"};
                if (validExtensions.Contains(ext) == false) return;

                var b = await Task.Run(() => LoadPicture(path));
                PictureView.Value = b;
                CanvasWidth.Value = string.Format("{0:0}", b.Width);
                CanvasHeight.Value = string.Format("{0:0}", b.Height);
            });
            Disposable.Add(DropCommand);
        }
        public void Dispose()
        {
            Debug.Print("Dispose()が呼ばれました。");
            Disposable.Dispose();
        }
    }
}

ファイル名:DragOverBehavior.cs

using System.Diagnostics;
using System.Xml.Serialization;
using System.Windows;
using Microsoft.Xaml.Behaviors;
using System;

namespace WpfSample18DD
{
    public class DragOverBehavior : Behavior<UIElement>
    {
        protected override void OnAttached()
        {
            base.OnAttached();
            this.AssociatedObject.DragOver += this.DragOver;
        }

        private void DragOver(object sender, DragEventArgs e)
        {
            if (e.Data.GetDataPresent(DataFormats.FileDrop))
            {
                e.Effects = DragDropEffects.Copy;
                e.Handled = true;
                return;
            }

            e.Effects = DragDropEffects.None;
            e.Handled = false;
        }

        protected override void OnDetaching()
        {
            base.OnDetaching();
            this.AssociatedObject.DragOver -= this.DragOver;
        }
    }
}

ファイル名:WindowCloseBehavior.cs

using System.Diagnostics;
using System.Xml.Serialization;
using System.Windows;
using Microsoft.Xaml.Behaviors;
using System;

namespace WpfSample18DD
{
    public class WindowCloseBehavior  : Behavior<Window>
    {
        protected override void OnAttached()
        {
            base.OnAttached();
            this.AssociatedObject.Closed += this.WindowClose;
        }

        private void WindowClose(object sender, EventArgs e)
        {
             (this.AssociatedObject.DataContext as IDisposable)?.Dispose();
        }

        protected override void OnDetaching()
        {
            base.OnDetaching();
            this.AssociatedObject.Closed -= this.WindowClose;
        }
    }
}

他のソースファイルに変更なし。

使い方

灰色部分に画像ファイルをDrag&Dropします。

画像が表示されます。

画像が大きい場合スクロールバーでスクロールすることが出来ます。

コメント