質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

ただいまの
回答率

88.91%

[WPF] StackPanelで並んでいるThumbをドラッグで幅を変更したい。

受付中

回答 1

投稿 編集

  • 評価
  • クリップ 0
  • VIEW 305

Y...M

score 13

前提・実現したいこと

StackPanelで並んでいるどのItemをドラッグしてもすべてのItemの幅を変更したい。

以下のXamlのようにItemsControlでStackPanelとThumbを使用しています。
ThumbのドラッグイベントでList内のItemの幅を変更しています。

用途・シナリオとしては、画像を並べて表示し、画像間のマージンをD&Dで変更するようなケースを想定しています。

発生している問題・エラーメッセージ

e.HorizontalChangeの移動量が大きく、正しくサイズ変更ができません。
ドラッグに追従して幅を変更する方法はありませんか?

該当のソースコード

<Window x:Class="BlankCoreApp2.Views.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:prism="http://prismlibrary.com/"
        xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
        prism:ViewModelLocator.AutoWireViewModel="True"
        Title="{Binding Title}" Height="350" Width="525" >
    <Grid>
        <ItemsControl ItemsSource="{Binding List}" Margin="50">
            <ItemsControl.ItemsPanel>
                <ItemsPanelTemplate>
                    <StackPanel Orientation="Horizontal" Background="Green"/>
                </ItemsPanelTemplate>
            </ItemsControl.ItemsPanel>
            <ItemsControl.ItemTemplate>
                <DataTemplate>
                    <Grid>
                        <Rectangle x:Name="rectangle"
                                   Width="{Binding Width}"
                                   Height="{Binding Height}"
                                   Stroke="Red"
                                   StrokeThickness="1"
                                   Fill="Yellow"/>
                        <Thumb  x:Name="rectangleThumb"
                            DataContext="{Binding DataContext, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type StackPanel}}}"
                            Background="Transparent"
                            Width="{Binding Width, ElementName=rectangle}"
                            Height="{Binding Height, ElementName=rectangle}"
                            Opacity="0">
                            <i:Interaction.Triggers>
                                <i:EventTrigger EventName="DragDelta">
                                    <prism:InvokeCommandAction Command="{Binding DragDeltaCommand}"/>
                                </i:EventTrigger>
                            </i:Interaction.Triggers>
                        </Thumb>
                    </Grid>
                </DataTemplate>
            </ItemsControl.ItemTemplate>
        </ItemsControl>
    </Grid>
</Window>
    public class MainWindowViewModel : BindableBase
    {
        private string _title = "Prism Application";
        public string Title
        {
            get { return _title; }
            set { SetProperty(ref _title, value); }
        }
        public enum Direction
        {
            Horizontal,
            Vertical
        }
        ObservableCollection<Item> _list = new ObservableCollection<Item>();
        public ObservableCollection<Item> List
        {
            get { return _list; }
            set { SetProperty(ref _list, value); }
        }
        public MainWindowViewModel()
        {
            for (int i = 0; i < 10; i++)
            {
                List.Add(new Item() { Width = 50, Height = 50 });
            }
        }

        private DelegateCommand<DragDeltaEventArgs> _dragDeltaCommand;
        public DelegateCommand<DragDeltaEventArgs> DragDeltaCommand
        {
            get
            {
                if (_dragDeltaCommand == null)
                    _dragDeltaCommand = new DelegateCommand<DragDeltaEventArgs>(e => MarginDragDelta(e, Direction.Horizontal));
                return _dragDeltaCommand;
            }
        }
        public void MarginDragDelta(DragDeltaEventArgs e, Direction direction)
        {
            List.Select(x => x.Width += (int)e.HorizontalChange).ToList();
        }
    }

    public class Item : BindableBase
    {
        private int _width = 0;
        private int _height = 0;


        public int Width
        {
            get { return _width; }
            set { SetProperty(ref _width, value); }
        }

        public int Height
        {
            get { return _height; }
            set { SetProperty(ref _height, value); }
        }
    }
  • 気になる質問をクリップする

    クリップした質問は、後からいつでもマイページで確認できます。

    またクリップした質問に回答があった際、通知やメールを受け取ることができます。

    クリップを取り消します

  • 良い質問の評価を上げる

    以下のような質問は評価を上げましょう

    • 質問内容が明確
    • 自分も答えを知りたい
    • 質問者以外のユーザにも役立つ

    評価が高い質問は、TOPページの「注目」タブのフィードに表示されやすくなります。

    質問の評価を上げたことを取り消します

  • 評価を下げられる数の上限に達しました

    評価を下げることができません

    • 1日5回まで評価を下げられます
    • 1日に1ユーザに対して2回まで評価を下げられます

    質問の評価を下げる

    teratailでは下記のような質問を「具体的に困っていることがない質問」、「サイトポリシーに違反する質問」と定義し、推奨していません。

    • プログラミングに関係のない質問
    • やってほしいことだけを記載した丸投げの質問
    • 問題・課題が含まれていない質問
    • 意図的に内容が抹消された質問
    • 過去に投稿した質問と同じ内容の質問
    • 広告と受け取られるような投稿

    評価が下がると、TOPページの「アクティブ」「注目」タブのフィードに表示されにくくなります。

    質問の評価を下げたことを取り消します

    この機能は開放されていません

    評価を下げる条件を満たしてません

    評価を下げる理由を選択してください

    詳細な説明はこちら

    上記に当てはまらず、質問内容が明確になっていない質問には「情報の追加・修正依頼」機能からコメントをしてください。

    質問の評価を下げる機能の利用条件

    この機能を利用するためには、以下の事項を行う必要があります。

回答 1

0

DragDeltaはクリックした位置からの差分なので、毎回足していくとどんどん伸びることになります。
前回のHorizontalChangeを引くか、クリックした時のWidthを覚えておくかになると思います。
そうなるとDragStartedDragCompletedの監視が必要になりそうです。

そもそもとして左端のThumbはいいですが、右のほうのThumbはサイズが変わったことで移動してしまいDragDeltaが大きく暴れてしまいます。
DragDeltaが使えないとなると、Mouse.GetPosition(Application.Current.MainWindow).Xとかになるでしょうが「どうなの?」って気はします。

もうすこし用途というかシナリオが分かればいい方法もありそうな気がしますが、かなり凝ったことをされているようなので説明しづらいでしょうね^^;


検証中はMVVMにこだわらずコードビハインドに書いていいと思います。
希望の動作になったら移せばいいので。

追記

一応それっぽくはなったのですが、非常に泥臭くなってしまいました^^;
これはビヘイビアにするのが正攻法でしょうね(ViewModelでやる処理じゃないですね)

注意

  • Prism.Coreだけ入れました
  • 構造は全部決め打ちです
  • 画像のサイズが同じという前提です(違うならサイズでなくてマージンで調整か?)
  • 縦線や半透明ピンクは確認用なので深い意味はないです
  • Rectangleなのも特に意味はないです
  • 画像も確認用なので特に意味はないです

Width Heightのバインディングが必要ないなら、ItemsControl自体のドラッグで全てのContentPresenterをリサイズするほうが簡単そうな気もします(位置補正まですると大差ないかもしれない)

<Window
  x:Class="Questions276037.MainWindow"
  xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
  xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
  Width="525"
  Height="350">
  <Window.Resources>
    <!--  個々のアイテム  -->
    <DataTemplate x:Key="itemTemplate">
      <Grid>
        <Image
          x:Name="image"
          HorizontalAlignment="Center"
          VerticalAlignment="Center"
          Source="{Binding Source}" />
        <Rectangle
          Width="{Binding Width}"
          Height="{Binding Height}"
          MinWidth="{Binding ActualWidth, ElementName=image}"
          MinHeight="{Binding ActualHeight, ElementName=image}"
          Fill="#4CFF0000"
          MouseDown="Rectangle_MouseDown"
          MouseMove="Rectangle_MouseMove"
          MouseUp="Rectangle_MouseUp" />
      </Grid>
    </DataTemplate>

    <!--  目印用グリッド線  -->
    <VisualBrush
      x:Key="lineBrush"
      TileMode="Tile"
      Viewbox="0,0,25,25"
      ViewboxUnits="Absolute"
      Viewport="0,0,25,25"
      ViewportUnits="Absolute">
      <VisualBrush.Visual>
        <Path Data="M0,0L0,25" Stroke="Gray" />
      </VisualBrush.Visual>
    </VisualBrush>
  </Window.Resources>

  <ItemsControl ItemTemplate="{StaticResource itemTemplate}" ItemsSource="{Binding List}">
    <ItemsControl.Template>
      <ControlTemplate>
        <ScrollViewer HorizontalScrollBarVisibility="Auto" VerticalScrollBarVisibility="Auto">
          <ItemsPresenter />
        </ScrollViewer>
      </ControlTemplate>
    </ItemsControl.Template>
    <ItemsControl.ItemsPanel>
      <ItemsPanelTemplate>
        <StackPanel Background="{StaticResource lineBrush}" Orientation="Horizontal" />
      </ItemsPanelTemplate>
    </ItemsControl.ItemsPanel>
  </ItemsControl>
</Window>
using System;
using System.Collections.ObjectModel;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Shapes;
using Prism.Mvvm;

namespace Questions276037
{
    public partial class MainWindow : Window
    {
        private readonly BitmapImage i1 = new BitmapImage(new Uri("https://teratail-v2.storage.googleapis.com/uploads/avatars/u13/132786/KnkDDC5A_thumbnail.jpg"));
        private readonly BitmapImage i2 = new BitmapImage(new Uri("https://teratail-v2.storage.googleapis.com/uploads/avatars/u10/105091/d8tKNb7D_thumbnail.jpg"));

        public ObservableCollection<Item> List { get; } = new ObservableCollection<Item>();

        public MainWindow()
        {
            InitializeComponent();

            for(var i = 0; i < 5; i++)
            {
                List.Add(new Item() { Width = 95, Height = 95, Source = i1, });
                List.Add(new Item() { Width = 95, Height = 95, Source = i2, });
            }
            DataContext = this;
        }

        private Point oldPos;
        private bool dragging;

        private void Rectangle_MouseDown(object sender, MouseButtonEventArgs e)
        {
            dragging = true;
            var rectangle = sender as Rectangle;
            rectangle.CaptureMouse();
            oldPos = Mouse.GetPosition(null);
        }
        private void Rectangle_MouseMove(object sender, MouseEventArgs e)
        {
            if(!dragging) return;

            var rectangle = sender as Rectangle;
            var newPos = Mouse.GetPosition(null);
            var deltaX = newPos.X - oldPos.X;
            oldPos = newPos;

            if(rectangle.Width + deltaX < rectangle.MinWidth) return; // 画像より小さくしない

            var contentPresenter = rectangle.FindAncestor<ContentPresenter>();
            var stackPanel = rectangle.FindAncestor<StackPanel>();
            var scrollViewer = rectangle.FindAncestor<ScrollViewer>();
            var itemsControl = rectangle.FindAncestor<ItemsControl>();

            foreach(Item item in itemsControl.Items)
                item.Width += deltaX;

            var offset = scrollViewer.TranslatePoint(new Point(), stackPanel); // StackPanelがScrollViewerからはみ出た長さ
            var index = itemsControl.ItemContainerGenerator.IndexFromContainer(contentPresenter);
            var total = (index + 1 - 0.5) * deltaX; // カーソル左側の伸びた分合計

            scrollViewer.ScrollToHorizontalOffset(offset.X + total); // スクロールバー調整
        }

        private void Rectangle_MouseUp(object sender, MouseButtonEventArgs e)
        {
            dragging = false;
            var rectangle = sender as Rectangle;
            rectangle.ReleaseMouseCapture();
            oldPos = new Point();
        }
    }

    public class Item : BindableBase
    {
        private double _width = 0;
        private double _height = 0;
        public double Width { get => _width; set => SetProperty(ref _width, value); }
        public double Height { get => _height; set => SetProperty(ref _height, value); }
        public BitmapImage Source { get; set; }
    }

    internal static class DependencyObjectExtensions
    {
        public static T FindAncestor<T>(this DependencyObject obj) where T : DependencyObject
        {
            do
            {
                obj = VisualTreeHelper.GetParent(obj);
                if(obj == null) return null;
            } while(!(obj is T));

            return obj as T;
        }
    }
}

投稿

編集

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2020/07/08 23:12

    おっしゃるとおり、右側の問題が一番困っています。

    用途・シナリオとしては、画像を並べて表示し、画像間のマージンをD&Dで変更するようなケースです。

    もし、良い案があればご教示お願いします。
    特にThumbにこだわっている訳ではありません。

    キャンセル

15分調べてもわからないことは、teratailで質問しよう!

  • ただいまの回答率 88.91%
  • 質問をまとめることで、思考を整理して素早く解決
  • テンプレート機能で、簡単に質問をまとめられる

関連した質問

同じタグがついた質問を見る