午後わてんのブログ

ベランダ菜園とWindows用アプリ作成(WPFとC#)

以前イベントで処理していたのをBindingにしてC#とXAMLで書いてみた

イベントで処理していたのをBindingに変更してC#XAMLの両方で書いてみたけど
どっちがいいのかよくわからん
イメージ 1

動作自体は前のときと同じ

Sliderの変更で画像の表示倍率を変更、画面より大きくなったらスクロールバーを表示
 
配置
Slider			拡大率指定用
ScrollViewer		スクロールバー表示用
	┗Canvas		ImageのScaleに合わせてサイズ変更
		┗Image	RenderTransformのScaleの変更で拡大
 

C#で書いたほう
画像の表示倍率のBinding
Target	Image.RenderTransform.ScaleTransform.ScaleX or Y
Source	Slider.Value

CanvasサイズのBinding
Target			Canvas.Width or Height
Source			Slider.Value
Converter			ValueConverter
ConverterParameter	Image.ActualWidth or ActualHeight
 

XAMLで書いたほう
画像の表示倍率のBindingはC#と同じ
 
CanvasサイズのBindingはMultiBinding
Target	Canvas.Width or Height
Source	Slider.Value
Source	Image.ActualWidth or ActualHeight
Converter	MultiValueConverter
 

BindingをC#で書いたほうのデザイン画面

f:id:gogowaten:20191213155325p:plain

<Window x:Class="_20190310_BindingScaleCanvasImage.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:_20190310_BindingScaleCanvasImage"
        mc:Ignorable="d"
        Title="MainWindow" Height="400" Width="500">
  <Grid Margin="0,30,0,0">
    
    <Grid.RowDefinitions>
      <RowDefinition Height="150"/>
      <RowDefinition/>
    </Grid.RowDefinitions>
    
    <StackPanel Grid.Row="0" VerticalAlignment="Center" Background="GhostWhite">

      <StackPanel Orientation="Horizontal">
        <TextBlock Text="{Binding ElementName=SliderScale, Path=Value, StringFormat=表示倍率 : 0}" Margin="10 0"/>
        <Slider Name="SliderScale" Value="1.0" Minimum="1.0" Maximum="5.0" SmallChange="1.0" LargeChange="1.0"
              IsMoveToPointEnabled="False" IsSnapToTickEnabled="True"
              Width="100"/>
      </StackPanel>
<!--#region 確認用なので不必要-->
      <StackPanel Orientation="Horizontal">
        <TextBlock Text="Canvas"/>
        <TextBlock Text="{Binding ElementName=MyCanvas1,Path=ActualWidth, StringFormat=ActualWidth : 0}" Margin="4,1"/>
        <TextBlock Text="{Binding ElementName=MyCanvas1,Path=ActualHeight, StringFormat=ActualHeight : 0}" Margin="4,1"/>        
      </StackPanel>
      <StackPanel Orientation="Horizontal">
        <TextBlock Text="ScrollViewer"/>
      </StackPanel>
      <StackPanel Orientation="Horizontal">
        <TextBlock Text="{Binding ElementName=MyScroll1, Path=ActualWidth, StringFormat=ActualWidth : 0}" Margin="4,1"/>
        <TextBlock Text="{Binding ElementName=MyScroll1, Path=ActualHeight, StringFormat=ActualHeight : 0}" Margin="4,1"/>
      </StackPanel>
      <StackPanel Orientation="Horizontal">
        <TextBlock Text="{Binding ElementName=MyScroll1, Path=ExtentWidth, StringFormat=ExtentWidth : 0}" Margin="4,1"/>
        <TextBlock Text="{Binding ElementName=MyScroll1, Path=ExtentHeight, StringFormat=ExtentHeight : 0}" Margin="4,1"/>
      </StackPanel>
      <StackPanel Orientation="Horizontal">
        <TextBlock Text="{Binding ElementName=MyScroll1, Path=ViewportWidth, StringFormat=ViewportWidth : 0}" Margin="4,1"/>
        <TextBlock Text="{Binding ElementName=MyScroll1, Path=ViewportHeight, StringFormat=ViewportHeight : 0}" Margin="4,1"/>
      </StackPanel>
      <StackPanel Orientation="Horizontal">
        <TextBlock Text="{Binding ElementName=MyScroll1, Path=HorizontalOffset, StringFormat=HorizontalOffset : 0}" Margin="4,1"/>
        <TextBlock Text="{Binding ElementName=MyScroll1, Path=VerticalOffset, StringFormat=VerticalOffset : 0}" Margin="4,1"/>
      </StackPanel>
      <StackPanel Orientation="Horizontal">
        <TextBlock Text="{Binding ElementName=MyScroll1, Path=ContentHorizontalOffset, StringFormat=ContentHorizontalOffset : 0}" Margin="4,1"/>
        <TextBlock Text="{Binding ElementName=MyScroll1, Path=ContentVerticalOffset, StringFormat=ContentVerticalOffset : 0}" Margin="4,1"/>
      </StackPanel>
<!--#endregion-->
    </StackPanel>

    <ScrollViewer Name="MyScroll1" Grid.Row="1" UseLayoutRounding="True"
                  VerticalScrollBarVisibility="Auto" HorizontalScrollBarVisibility="Auto">
      <Canvas Name="MyCanvas1">
        <Image Name="MyImage1" Stretch="None" Canvas.Top="0" Canvas.Left="0"/>
      </Canvas>
    </ScrollViewer>

  </Grid>
</Window>
 
using System;
using System.Globalization;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Media;
using System.Windows.Media.Imaging;

namespace _20190310_BindingScaleCanvasImage
{
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();

            ContentRendered += MainWindow_ContentRendered;

            //表示する画像ファイルのパス
            string filePath1 = @"D:\ブログ用\チェック用2\NEC_6221_2019_02_24_午後わてん_16colors_trim.png";

            //Imageに画像表示
            MyImage1.Source = new BitmapImage(new Uri(filePath1));
        }
        //アプリが表示された直後
        private void MainWindow_ContentRendered(object sender, EventArgs e)
        {
            //Image拡大表示の補間法指定、今回はニアレストネイバー法
            RenderOptions.SetBitmapScalingMode(MyImage1, BitmapScalingMode.NearestNeighbor);

            //ScaleTransform作成してImageのRenderTransformに指定
            //これをしないと拡大できない
            var st = new ScaleTransform();
            MyImage1.RenderTransform = st;


            //ここからBinding
            //ソース SliderのValue
            //ターゲット ImageのScaleTransformのXとY
            var b = new Binding();
            b.Source = SliderScale;
            b.Path = new PropertyPath(Slider.ValueProperty);
            BindingOperations.SetBinding(st, ScaleTransform.ScaleXProperty, b);
            BindingOperations.SetBinding(st, ScaleTransform.ScaleYProperty, b);

            //ソース Slider.Value
            //ターゲット CanvasのWidth
            //Canvas.Width = Slider.Value * Image.ActualWidthをにするためにConverter指定
            //ParameterにImage.Width
            b = new Binding();
            b.Source = SliderScale;
            b.Path = new PropertyPath(Slider.ValueProperty);
            b.ConverterParameter = MyImage1.ActualWidth;
            b.Converter = new MyConverter();
            MyCanvas1.SetBinding(WidthProperty, b);

            //↑のHeight版
            b = new Binding();
            b.Source = SliderScale;
            b.Path = new PropertyPath(Slider.ValueProperty);
            b.ConverterParameter = MyImage1.ActualHeight;
            b.Converter = new MyConverter();
            MyCanvas1.SetBinding(HeightProperty, b);

        }

    }
    //Value * parameterを返す
    public class MyConverter : IValueConverter
    {
        public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
        {           
            return (double)value * (double)parameter;
        }

        public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
        {
            throw new NotImplementedException();
        }
    }

}
 
 
BindingをXAMLで書いたほうのデザイン画面

f:id:gogowaten:20191213155416p:plain

<Window x:Class="_20190310_BindingScaleCanvasImageXAML.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:_20190310_BindingScaleCanvasImageXAML"
        mc:Ignorable="d"
        Title="MainWindow" Height="450" Width="800">
  <Window.Resources>
    <local:MyMulti x:Key="myMultiConverter"/>
  </Window.Resources>

  <Grid Margin="0,30,0,0">

    <Grid.RowDefinitions>
      <RowDefinition Height="140"/>
      <RowDefinition/>
    </Grid.RowDefinitions>

    <StackPanel Grid.Row="0" VerticalAlignment="Center">

      <StackPanel Orientation="Horizontal">
        <TextBlock Text="{Binding ElementName=SliderScale, Path=Value, StringFormat=表示倍率 : 0}" Margin="10 0"/>
        <Slider Name="SliderScale" Value="1.0" Minimum="1.0" Maximum="5.0" SmallChange="1.0" LargeChange="1.0"
              IsMoveToPointEnabled="False" IsSnapToTickEnabled="True"
              Width="100"/>
      </StackPanel>
      <!--#region 確認用なので不必要-->
      <StackPanel Orientation="Horizontal">
        <TextBlock Text="MyCanvas1"/>
        <TextBlock Text="{Binding ElementName=MyCanvas1,Path=ActualWidth, StringFormat=ActualWidth : 0}" Margin="4,1"/>
        <TextBlock Text="{Binding ElementName=MyCanvas1,Path=ActualHeight, StringFormat=ActualHeight : 0}" Margin="4,1"/>
      </StackPanel>
      <StackPanel Orientation="Horizontal">
        <TextBlock Text="ScrollViewer"/>
        <TextBlock Text="{Binding ElementName=MyScroll1, Path=ActualWidth, StringFormat=ActualWidth : 0}" Margin="4,1"/>
        <TextBlock Text="{Binding ElementName=MyScroll1, Path=ActualHeight, StringFormat=ActualHeight : 0}" Margin="4,1"/>
      </StackPanel>
      <StackPanel Orientation="Horizontal">
        <TextBlock Text="{Binding ElementName=MyScroll1, Path=ExtentWidth, StringFormat=ExtentWidth : 0}" Margin="4,1"/>
        <TextBlock Text="{Binding ElementName=MyScroll1, Path=ExtentHeight, StringFormat=ExtentHeight : 0}" Margin="4,1"/>
      </StackPanel>
      <StackPanel Orientation="Horizontal">
        <TextBlock Text="{Binding ElementName=MyScroll1, Path=ViewportWidth, StringFormat=ViewportWidth : 0}" Margin="4,1"/>
        <TextBlock Text="{Binding ElementName=MyScroll1, Path=ViewportHeight, StringFormat=ViewportHeight : 0}" Margin="4,1"/>
      </StackPanel>
      <StackPanel Orientation="Horizontal">
        <TextBlock Text="{Binding ElementName=MyScroll1, Path=HorizontalOffset, StringFormat=HorizontalOffset : 0}" Margin="4,1"/>
        <TextBlock Text="{Binding ElementName=MyScroll1, Path=VerticalOffset, StringFormat=VerticalOffset : 0}" Margin="4,1"/>
      </StackPanel>
      <StackPanel Orientation="Horizontal">
        <TextBlock Text="{Binding ElementName=MyScroll1, Path=ContentHorizontalOffset, StringFormat=ContentHorizontalOffset : 0}" Margin="4,1"/>
        <TextBlock Text="{Binding ElementName=MyScroll1, Path=ContentVerticalOffset, StringFormat=ContentVerticalOffset : 0}" Margin="4,1"/>
      </StackPanel>
      <!--#endregion-->
    </StackPanel>

    <ScrollViewer Name="MyScroll1" Grid.Row="1" UseLayoutRounding="True"
                  VerticalScrollBarVisibility="Auto" HorizontalScrollBarVisibility="Auto">
      <Canvas Name="MyCanvas1" >

        <Canvas.Width>
          <MultiBinding Converter="{StaticResource myMultiConverter}">
            <Binding ElementName="SliderScale" Path="Value"/>
            <Binding Path="ActualWidth" ElementName="MyImage1"/>
          </MultiBinding>
        </Canvas.Width>
        <Canvas.Height>
          <MultiBinding Converter="{StaticResource myMultiConverter}">
            <Binding ElementName="SliderScale" Path="Value"/>
            <Binding Path="ActualHeight" ElementName="MyImage1"/>
          </MultiBinding>
        </Canvas.Height>

        <Image Name="MyImage1" Stretch="None" Canvas.Top="0" Canvas.Left="0" RenderOptions.BitmapScalingMode="NearestNeighbor">
          <Image.RenderTransform>
            <ScaleTransform ScaleX="{Binding ElementName=SliderScale, Path=Value}"
                            ScaleY="{Binding ElementName=SliderScale, Path=Value}">
            </ScaleTransform>
          </Image.RenderTransform>
        </Image>
      </Canvas>
    </ScrollViewer>

  </Grid>
</Window>
 
 
 
using System;
using System.Windows;
using System.Windows.Data;
using System.Windows.Media.Imaging;
using System.Globalization;

namespace _20190310_BindingScaleCanvasImageXAML
{
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
            
            //表示する画像ファイルのパス
            string filePath1 = @"D:\ブログ用\チェック用2\NEC_6221_2019_02_24_午後わてん_half.jpg";

            //Imageに画像表示
            MyImage1.Source = new BitmapImage(new Uri(filePath1));

        }
    }

    public class MyMulti : IMultiValueConverter
    {
        public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
        {           
            return (double)values[0] * (double)values[1];
        }

        public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
        {
            throw new NotImplementedException();
        }
    }
}
 
 
 
前回(半年前)はSliderのValueChangedイベント発生時にImageのScaleを変更するコードをC#で書いていた
これをBindingに変更してみた
イメージ 2
 
XAMLで書くとこうで、C#で書くと↓
 
イメージ 3
この場合はC#よりXAMLで書いたほうがラクかも
 
Image(画像)の拡大はこれでおk
 
 
Canvasのサイズ変更
スクロールバーを画像の拡大に合わせて表示するには、Canvasのサイズ変更をする必要がある
前回は

f:id:gogowaten:20191213155505p:plain

SliderのValueChangedイベント発生時に処理を書いていた
これをBindingにしたのが
 

f:id:gogowaten:20191213155523p:plain

これは逆にめんどくさくなった気がする
 
Canvas.Widthとして欲しい値は
Canvas.Width = Slider.Value * Image.ActualWidth
でソースとなるSlider.ValueImage.ActualWidthを掛けた値
ってことは変換(掛け算)する必要があるので
63~74行目にMyConverter作成
これは引数のvalue(ソースの値)に引数のparameterを掛けた値を返すだけのもの
この引数parameterにImage.ActualWidthを渡せばいいので
 
48行目、BindingのConverterにMyConverterを指定
47行目、Image.ActualWidthをBindingのConverterParameterに指定
 
 
C#ではこれでできたのでXAMLで書こうとしたけど書き方がわからなかった
ConverterParameterにMyImage1.ActualWidthを指定する方法がわからない

f:id:gogowaten:20191213155538p:plain

 
なので
MultiBindingを使う方法
Slider.Value と Image.ActualWidthの2つをソースにすればいいんじゃないかと
そうすればConverterParameterを使う必要がなくなる
 
2つのソースの値を掛け算して返すConverter、MyMultiを用意しておいて
イメージ 8
 
XAMLで使えるようにしておいて
イメージ 9
イメージ 7
これでできた!
できたはできたけどやっぱりめんどくさい気がする
これなら前回のようにイベント発生時にC#で書いたほうがラクかも?
 
 
MultiBindingは入力候補が出ない
普通なら
イメージ 11
ElementNameを入力しているところ
入力候補一覧が表示される、右下
 
MultiBindingのところだけは入力候補が出ない
イメージ 10
間違っているんじゃないかって不安になる
 
参照したところ
c# - Multibinding generates "Cannot set MultiBinding because MultiValueConverter must be specified" - Stack Overflow
https://stackoverflow.com/questions/19510196/multibinding-generates-cannot-set-multibinding-because-multivalueconverter-must
 
 
 
ギットハブ
BindingをC#で書いたほう
XAMLで書いたほう
 
今回のアプリのダウンロード先
毎回ファイル自体をアップロードして、ただのファイル置き場になっていたgithub、少し使い方がわかって同期ボタンで更新できるようになった
最初にリポジトリgithubに作って、それをVisual Studioから複製すればよかったのね
 
関連記事
半年前、2018/11/15
WPF、スクロールバーの同期、2つの画像を並べて拡大して見比べたい、ScrollViewer ( ソフトウェア ) - 午後わてんのブログ - Yahoo!ブログ
https://blogs.yahoo.co.jp/gogowaten/15754500.html
 
2年前、2017/6/23
WPF、Borderの背景色(Background.Brush)とスライダーの値を双方向バインディング? ( ソフトウェア ) - 午後わてんのブログ - Yahoo!ブログ
https://blogs.yahoo.co.jp/gogowaten/14987741.html
MultiBindingをVBで書いてる、結構いろいろ試していたんだなあ、あんまり憶えていない