C#のWPFでDrag&Dropで画像を表示する。

C# コンピュータ
C#

過去に作成したとあるWPFプロジェクトがいつの間にかビルドすることが出来なくなっており、原因を調べようとしましたがWPFで忘れていることが多く、再度学習しなおしたいと思います。

プロジェクトの作成

mkdir DDImgDraw01
cd DDImgDraw01
dotnet new wpf
dotnet add package Microsoft.Xaml.Behaviors.Wpf
dotnet add package ReactiveProperty.WPF

ソースコード

using System.Windows;
using Microsoft.Xaml.Behaviors;

namespace DDImgDraw01.Behavior;
/// <summary>
/// ドラックオーバービヘイビア
/// </summary>
public class DragOverBehavior : Behavior<UIElement>
{
    /// <summary>
    /// アタッチ
    /// </summary>
    protected override void OnAttached()
    {
        base.OnAttached();
        AssociatedObject.DragOver += DragOver;
    }
    /// <summary>
    /// ドラックオーバーイベント
    /// </summary>
    /// <param name="sender">イベントの送信元</param>
    /// <param name="e">イベントの引数</param>
    private void DragOver(object? sender, DragEventArgs e)
    {
        // ドラックされたデータがファイルの場合
        if (e.Data.GetDataPresent(DataFormats.FileDrop))
        {
            // エフェクトをコピーにセットしもどる。
            e.Effects = DragDropEffects.Copy;
            e.Handled = true;
            return;
        }

        // 通常のエフェクトのNoneをセット
        e.Effects = DragDropEffects.None;
        e.Handled = false;
    }
    /// <summary>
    /// デタッチ
    /// </summary>
    protected override void OnDetaching()
    {
        base.OnDetaching();
        AssociatedObject.DragOver -= DragOver;
    }
}
using System.Windows;
using Microsoft.Xaml.Behaviors;

namespace DDImgDraw01.Behavior;
/// <summary>
/// ウィンドウクローズのビヘイビア
/// </summary>
public class WindowCloseBehavior  : Behavior<Window>
{
    /// <summary>
    /// アタッチ
    /// </summary>
    protected override void OnAttached()
    {
        base.OnAttached();
        AssociatedObject.Closed += WindowClose;
    }
    /// <summary>
    /// ウィンドウクローズイベント
    /// </summary>
    /// <param name="sender">オブジェクトの送信元(Window)</param>
    /// <param name="e">イベント引数</param>
    private void WindowClose(object? sender, EventArgs e)
    {
        // コンテキスト(VM)がDisposableならばDispose();
        (AssociatedObject.DataContext as IDisposable)?.Dispose();
    }
    /// <summary>
    /// デタッチ
    /// </summary>
    protected override void OnDetaching()
    {
        base.OnDetaching();
        AssociatedObject.Closed -= WindowClose;
    }
}
using System.Reactive.Linq;
using System.Windows;
using Reactive.Bindings.Interactivity;

namespace DDImgDraw01.Converter;
/// <summary>
/// ドラックイベントの引数をファイルの一覧に変換するコンバーター 
/// </summary>
public class DragEventArgsToFilesConverter
: ReactiveConverter<DragEventArgs?, string[]?>
{
    /// <summary>
    /// 変換処理
    /// </summary>
    /// <param name="source">ドラックイベントの引数</param>
    /// <returns>ファイルのパスの一覧がセットされた文字配列</returns> 
    protected override IObservable<string[]?> OnConvert(IObservable<DragEventArgs?> source) => source
        .Select( e =>
        {
            if (e is null || e.Data is null) return null;
            if (e.Data.GetDataPresent(DataFormats.FileDrop) == false) return null;
            var data = e.Data.GetData(DataFormats.FileDrop);
            if (data is null) return null;
            string[]? files = data as string[];
            return files;
        });
}
using System.IO;
using System.Windows.Media.Imaging;

namespace DDImgDraw01.Model;

/// <summary>
/// イメージモデル
/// </summary>
public class ImgModel
{
    /// <summary>
    /// ローカルファイルを読み込みBitmapImageを返す。
    /// </summary>
    /// <param name="file">読み込む画像ファイルのパス</param>
    /// <returns>BitmapImage</returns>
    public static async Task<BitmapImage> LoadFromFileAsync(string file)
    {

        using var fs = new FileStream(file, FileMode.Open, FileAccess.Read);
        using var ms = new MemoryStream();
        await fs.CopyToAsync(ms);
        BitmapImage bi = await Task.Run(()=>
        {
            BitmapImage bi = new();
            ms.Seek(0, SeekOrigin.Begin);

            bi.BeginInit();
            bi.CacheOption = BitmapCacheOption.OnLoad;
            bi.StreamSource = ms;
            bi.EndInit();
            bi.Freeze();

            ms.SetLength(0);
            return bi;
        });

        return bi;
    }
}
using System.ComponentModel;
using System.Reactive.Disposables;
using System.Windows.Media.Imaging;
using DDImgDraw01.Model;
using Reactive.Bindings;
using Reactive.Bindings.Extensions;

namespace DDImgDraw01.ViewModel;

/// <summary>
/// メインウィンドウのViewModel
/// </summary>
public class MainWindowViewModel : INotifyPropertyChanged, IDisposable
{
    // INotifyPropertyChanged
    public event PropertyChangedEventHandler? PropertyChanged;
    protected virtual void OnPropertyChanged(string name) =>
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
    // IDisposable
    private CompositeDisposable Disposable { get; } = [];

    /**************************************************************************
    * プロパティ
    **************************************************************************/
    public ReactiveProperty<BitmapSource> PictureView { get; private set; }
    public ReactiveCommand<string[]?> DropCommand { get; }

    /// <summary>
    /// コンストラクタ
    /// </summary>
    public MainWindowViewModel()
    {
        // INotifyPropertyChanged
        PropertyChanged += (sender, e) => {};

        PictureView = new ();
        Disposable.Add(PictureView);

        DropCommand = new ();
        DropCommand.Subscribe(async files =>
        {
            if (files is null || files.Length == 0) return;
            string file = files[0];

            PictureView.Value = await ImgModel.LoadFromFileAsync(file);
        }).AddTo(Disposable);
    }
    /// <summary>
    /// Dispose
    /// </summary>
    public void Dispose() => Disposable.Dispose();
}
<Window x:Class="DDImgDraw01.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:DDImgDraw01"
        xmlns:local_behavior="clr-namespace:DDImgDraw01.Behavior"
        xmlns:local_viewmodel="clr-namespace:DDImgDraw01.ViewModel"
        xmlns:local_converter="clr-namespace:DDImgDraw01.Converter"
        mc:Ignorable="d"
        Title="MainWindow" Height="450" Width="800">
    <!-- データコンテキストをMainWindowViewModelに -->
    <Window.DataContext>
        <local_viewmodel:MainWindowViewModel />
    </Window.DataContext>
    <!-- ウィンドウクローズイベントで呼び出すビヘイビア -->
    <i:Interaction.Behaviors>
        <local_behavior:WindowCloseBehavior />
    </i:Interaction.Behaviors>
    <Grid>
        <StackPanel>
            <!-- CanvasのWidthとHeightはStackPanelのWidthとHeightとバインド -->
            <Canvas
                Width="{Binding ActualWidth, RelativeSource={RelativeSource FindAncestor, AncestorType=StackPanel}}"
                Height="{Binding ActualHeight, RelativeSource={RelativeSource FindAncestor, AncestorType=StackPanel}}"
                Background="Gray"
                AllowDrop="True">
                <!-- ドラックオーバーイベントで呼び出すビヘイビア -->
                <i:Interaction.Behaviors>
                    <local_behavior:DragOverBehavior />
                </i:Interaction.Behaviors>
                <!-- ドラックドロップイベントのトリガーー -->
                <i:Interaction.Triggers>
                    <i:EventTrigger EventName="Drop">
                        <!-- DropCommandとバインディング -->
                        <interactivity:EventToReactiveCommand Command="{Binding DropCommand}">
                            <!-- ドラックイベント引数からファイルの一覧へコンバーターで変換 -->
                            <local_converter:DragEventArgsToFilesConverter />
                        </interactivity:EventToReactiveCommand>
                    </i:EventTrigger>
                </i:Interaction.Triggers>
                <!-- PictureView.Valueとバインディング -->
                <Image
                    Stretch="None"
                    Source="{Binding PictureView.Value}">
                </Image>
            </Canvas>
        </StackPanel>
    </Grid>
</Window>
using System.Windows;

namespace DDImgDraw01;

/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
    public MainWindow()
    {
        InitializeComponent();
    }
}

感想

ソースファイルを出来る限り分割してみました。

コメント