C# と VB.NET の質問掲示板

ASP.NET、C++/CLI、Java 何でもどうぞ

C# と VB.NET の入門サイト

DataGridのセルをUserControlにする

[トピック内 6 記事 (1 - 6 表示)]  << 0 >>

■88753 / inTopicNo.1)  DataGridのセルをUserControlにする
  
□投稿者/ りんく (1回)-(2018/09/24(Mon) 13:03:46)

分類:[C#] 

Visual Studio 2017
C#
WPF
using Reactive.Bindings;

■ 前提
 自身で作成した NumericUpDown というコントロールがあります。
 DataGrid セル を 条件によって NumericUpDown や CheckBox などにしたいです。
  → CustomTemplate を使用して実現できています。
 NumericUpDown は DataGrid のセルとしてではなく、
 通常のコントロールとしても使用可能で、こちらの動作確認もできています。

■ 問題点
SettingControl の DataGrid で
    <DataTemplate x:Key="updown">
        <local:NumericUpDown Min="{Binding Min}" Max="{Binding Max}" Value="{Binding Value}"/>
    </DataTemplate>としていますが、
このとき、NumericUpDown になるセルを編集モードにしても NumericUpDown.xaml.cs の
    // 3. 依存プロパティが変更されたとき呼ばれるコールバック関数の定義
    private static void OnValueChanged(DependencyObject obj, DependencyPropertyChangedEventArgs e) {}
    private static void OnMinChanged(DependencyObject obj, DependencyPropertyChangedEventArgs e) {}
    private static void OnMaxChanged(DependencyObject obj, DependencyPropertyChangedEventArgs e) {}
が呼ばれません。
Setting クラスのプロパティをReactivePropertyにしてみたりしたのですが呼ばれませんでした。
※通常のコントロールとして配置し、バインドしたときにはコールバックが呼ばれました。

どのようにすれば コールバックが呼ばれるでしょうか?
申し訳ありませんが、よろしくお願いいたします。

-- Main Xaml--
    <local:SettingControl DataContext="{Binding Setting}"/>

-- MainViewModel --
    settings = new ReactiveCollection<Setting>();
    settings.Add(new SystemSetting() { Title = "項目1", Value = 10, Min = 0, Max = 50 });
    settings.Add(new SystemSetting() { Title = "項目2", Value = 30, Min = 0, Max = 100 });
    Settings = setting;

    public ReactiveCollection<Setting> Settings { get; set; } = new ReactiveCollection<Setting>();

-- Setting クラス --
    public class Setting
    {
        public string Title { get; set; }
        public object Value { get; set; }
        public int Min { get; set; }
        public int Max { get; set; }
    }


-- UserControl SettingControl Xaml --
    <DataGrid AutoGenerateColumns="False" Background="White" ColumnHeaderHeight="40" HeadersVisibility="All" CanUserAddRows="False" ItemsSource="{Binding}">
        <i:Interaction.Behaviors>
            <b:BubbleScrollBehavior/>
        </i:Interaction.Behaviors>
        <DataGrid.Resources>
            <DataTemplate x:Key="updown">
                <local:NumericUpDown Min="{Binding Min}" Max="{Binding Max}" Value="{Binding Value}"/>
            </DataTemplate>
            <DataTemplate x:Key="block">
                <TextBlock Text="{Binding Value}" />
            </DataTemplate>
            <local:CustomEditingCellTemplateSelector x:Key="editSelector" />
            <local:CustomCellTemplateSelector x:Key="viewSelector" />
        </DataGrid.Resources>
        <DataGrid.Columns>
            <DataGridTextColumn Header="項目" IsReadOnly="True" Width="300" Binding="{Binding Title}" CanUserSort="False"/>
            <DataGridTemplateColumn Header="内容" IsReadOnly="False" Width="200" 
                                    CellEditingTemplateSelector="{StaticResource editSelector}" 
                                    CellTemplateSelector ="{StaticResource viewSelector}">
            </DataGridTemplateColumn>
        </DataGrid.Columns>
    </DataGrid>

-- UserControl SettingControl CustomTemplate --
    public class CustomCellTemplateSelectorBase : DataTemplateSelector
    {
        protected DataGrid GetDataGrid(DependencyObject container)
        {
            DataGrid dgv = null;
            var dpo = container;
            while (dpo != null)
            {
                dgv = dpo as DataGrid;
                if (dgv != null)
                {
                    return dgv;
                }
                dpo = VisualTreeHelper.GetParent(dpo);
            }
            return null;
        }
    }

    public class CustomCellTemplateSelector : CustomCellTemplateSelectorBase
    {
        public override DataTemplate SelectTemplate(object item, DependencyObject container)
        {
            var dgv = GetDataGrid(container);
            if (dgv != null)
            {
                var setting = item as Setting;
                {
                    return (DataTemplate)dgv.FindResource("block");
                }
            }

            return base.SelectTemplate(item, container);
        }
    }

    public class CustomEditingCellTemplateSelector : CustomCellTemplateSelectorBase
    {
        public override DataTemplate SelectTemplate(object item, DependencyObject container)
        {
            var dgv = GetDataGrid(container);
            if (dgv != null)
            {
                var setting = dgv.CurrentCell.Item as Setting;
                if (setting != null)
                {
                    - 省略
                    //編集用のテンプレートとしてNumericUpDownを返す
                    return (DataTemplate)dgv.FindResource("updown");
                }
            }

            return base.SelectTemplate(item, container);
        }
    }

※書ききれないので返信欄に記載します

引用返信 編集キー/
■88754 / inTopicNo.2)  Re[1]: DataGridのセルをUserControlにする
□投稿者/ りんく (2回)-(2018/09/24(Mon) 13:04:36)
つづき
-- NumericUpDown Xaml --
    <UserControl x:Class="UI.Controls.NumericUpDown"
                - 省略
                mc:Ignorable="d" Loaded="UserControl_Loaded">
        <Grid Background="White">
            <Grid.ColumnDefinitions>
                <ColumnDefinition/>
                <ColumnDefinition Width="15"/>
            </Grid.ColumnDefinitions>
            <TextBox x:Name="valueText" Text="{Binding ValueText.Value, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
                    BorderBrush="Transparent" TextChanged="TextBox_TextChanged" >
            </TextBox>
            <Grid Grid.Column="1">
                <Grid.RowDefinitions>
                    <RowDefinition/>
                    <RowDefinition/>
                </Grid.RowDefinitions>
                <Button x:Name="upButton" Content="▲" FontSize="3" BorderBrush="Transparent" Command="{Binding UpCommand}"/>
                <Button x:Name="downButton" Grid.Row="1" Content="▼" FontSize="3" BorderBrush="Transparent" Command="{Binding DownCommand}"/>
            </Grid>
        </Grid>
    </UserControl>

-- NumericUpDown Xaml.cs --
    // 1. 依存プロパティの作成
    public static readonly DependencyProperty ValueProperty =
        DependencyProperty.Register("Value",
                                    typeof(object),
                                    typeof(NumericUpDown),
                                    new FrameworkPropertyMetadata("Value", new PropertyChangedCallback(OnValueChanged)));
    public static readonly DependencyProperty MinProperty =
        DependencyProperty.Register("Min",
                                    typeof(int),
                                    typeof(NumericUpDown),
                                    new FrameworkPropertyMetadata("Min", new PropertyChangedCallback(OnMinChanged)));
    public static readonly DependencyProperty MaxProperty =
        DependencyProperty.Register("Max",
                                    typeof(int),
                                    typeof(NumericUpDown),
                                    new FrameworkPropertyMetadata("Max", new PropertyChangedCallback(OnMaxChanged)));

    // 2. CLI用プロパティを提供するラッパー
    public object Value
    {
        get { return (int)GetValue(ValueProperty); }
        set { SetValue(ValueProperty, value); }
    }
    public int Min
    {
        get { return (int)GetValue(MinProperty); }
        set { SetValue(MinProperty, value); }
    }
    public int Max
    {
        get { return (int)GetValue(MaxProperty); }
        set { SetValue(MaxProperty, value); }
    }

    // 3. 依存プロパティが変更されたとき呼ばれるコールバック関数の定義
    private static void OnValueChanged(DependencyObject obj, DependencyPropertyChangedEventArgs e)
    {
        - 省略
    }
    private static void OnMinChanged(DependencyObject obj, DependencyPropertyChangedEventArgs e)
    {
        - 省略
    }
    private static void OnMaxChanged(DependencyObject obj, DependencyPropertyChangedEventArgs e)
    {
        - 省略
    }


    public NumericUpDown()
    {
        InitializeComponent();

        this.DataContext = new ViewModels.NumericUpDownViewModel();
    }
    private void TextBox_TextChanged(object sender, TextChangedEventArgs e)
    {
        Value = valueText.Text;
    }

    private void UserControl_Loaded(object sender, RoutedEventArgs e)
    {
        NumericUpDown ctrl = sender as NumericUpDown;
        var vm = this.DataContext as ViewModels.NumericUpDownViewModel;
        if (vm != null)
        {
            vm.Min = ctrl.Min;
            vm.Max = ctrl.Max;
            vm.ValueText.Value = ctrl.Value.ToString();
        }
    }

-- NumericUpDownViewModel --
    public NumericUpDownViewModel()
    {
        // 値を上げる
        UpCommand.Subscribe(_ =>
        {
            if (string.IsNullOrEmpty(ValueText.Value))
            {
                ValueText.Value = $"{Min:D02}";
                return;
            }
            var value = int.Parse(ValueText.Value);
            value += 1;
            if (value > Max)
            {
                value = Min;
            }
            ValueText.Value = $"{value:D02}";
        });

        // 値を下げる
        DownCommand.Subscribe(_ =>
        {
            if (string.IsNullOrEmpty(ValueText.Value))
            {
                ValueText.Value = $"{Max:D02}";
                return;
            }
            var value = int.Parse(ValueText.Value);
            value -= 1;
            if (Min > value)
            {
                value = Max;
            }
            ValueText.Value = $"{value:D02}";
        });
    }

    public ReactiveProperty<string> ValueText { get; } = new ReactiveProperty<string>();

    public ReactiveCommand UpCommand { get; } = new ReactiveCommand();
    public ReactiveCommand DownCommand { get; } = new ReactiveCommand();

    public int Min { get; set; }
    public int Max { get; set; }

引用返信 編集キー/
■88762 / inTopicNo.3)  Re[2]: DataGridのセルをUserControlにする
□投稿者/ Hongliang (705回)-(2018/09/25(Tue) 15:39:22)
前提条件。Windows 7, .NET 4.7.1。
まず、Rxを入れていない等で、一部コードを多少いじりました。
このコードぐらいなら意味は変わっていないと思います。
・ReactiveCollection -> ObservableCollection
・i:Interaction.Behaviors -> 削除
・ReactiveProperty -> INotifyPropertyChangedを実装した独自クラス
・ReactiveCommand -> ICommandを実装した独自クラス

また実行時例外になる部分もいじりました。
・各依存関係プロパティの定義部分で、new FrameworkPropertyMetadataの第1引数が
 文字列"VALUE"/"MIN"/"MAX"になってるのを、1/0/100に変更

それから多少の手抜きのために、
・SettingControlクラスは定義せず、直接MainWindowにDataGridを配置

さて、私の環境では問題なく、編集モードに入った際にNumericUpDown::OnValueChangedが発生しています。
// ただし、ValuePropertyの初期値(前述のMetadata第1引数)が数字文字列で、
// Setting::ValueをToStringした値と一致した場合を除く。
// この場合、UserControl_Loadedのvm.ValueText.Value = ctrl.Value.ToString()が同値のため、
// PropertyChangedイベントが発生せず、結果としてOnValueChangedも発生しません。

問題なのは、Settingオブジェクトとのバインディングができていないことです。
NumericUpDownはコードで自身のDataContextを設定しているため、
<local:NumericUpDown Min="{Binding Min}" Max="{Binding Max}" Value="{Binding Value}"/>
の3つのバインディングのソースは全てNumericUpDownViewModelになってしまっています。
// バインディングのエラーをトレースに出すように設定しているなら、
// NumericUpDownViewModelにValueというプロパティが存在していないというエラーが出力されます。

一般的かどうかは知りませんが、NumericUpDown内のバインディングにおいては、
自身ではなく、最上位子要素Grid辺りのDataContextで扱うようにすれば、
NumericUpDownへの外部からのバインディングと内部のバインディングを別々に扱えるようになります。

引用返信 編集キー/
■88779 / inTopicNo.4)  Re[3]: DataGridのセルをUserControlにする
□投稿者/ りんく (4回)-(2018/09/26(Wed) 10:36:27)
No88762 (Hongliang さん) に返信
返信遅くなり申し訳ありません。
ご返信ありがとうございます。

> <local:NumericUpDown Min="{Binding Min}" Max="{Binding Max}" Value="{Binding Value}"/>
> の3つのバインディングのソースは全てNumericUpDownViewModelになってしまっています。
こういうことだったのですね。
バインディングのエラーをトレースに出すように設定したところ、
NumericUpDownViewModelにValueというプロパティが存在していないというエラーが出力されました。


> 一般的かどうかは知りませんが、NumericUpDown内のバインディングにおいては、
> 自身ではなく、最上位子要素Grid辺りのDataContextで扱うようにすれば、
> NumericUpDownへの外部からのバインディングと内部のバインディングを別々に扱えるようになります。

NumericUpDownが置いてあるDataGrid.ItemsSourceのMin,Max,Valueをバインドするには
どうしたらよいのでしょうか?

> 自身ではなく、最上位子要素Grid辺りのDataContextで扱うようにすれば、
最上位子要素とはSettingControl.xamlの子要素でしょうか?

 Min="{Binding RelativeSource={RelativeSource AncestorLevel=1,AncestorType={x:Type UserControl}},Path=DataContext.Min}"

こんな感じにしてみましたがダメでした。

引用返信 編集キー/
■88780 / inTopicNo.5)  Re[4]: DataGridのセルをUserControlにする
□投稿者/ Hongliang (706回)-(2018/09/26(Wed) 11:10:54)
> NumericUpDownが置いてあるDataGrid.ItemsSourceのMin,Max,Valueをバインドするには
> どうしたらよいのでしょうか?
> 
>>自身ではなく、最上位子要素Grid辺りのDataContextで扱うようにすれば、
> 最上位子要素とはSettingControl.xamlの子要素でしょうか?

NumericUpDownの内部の話です。
<UserControl x:Class="UI.Controls.NumericUpDown"
             ...>
    <Grid Background="White" x:Name="Root">

public NumericUpDown()
{
    InitializeComponent();

    // 内部のVMは内部の要素に。
    this.Root.DataContext = new ViewModels.NumericUpDownViewModel();
    // これならNumericUpDown自体はDataContextが設定されてないので、
    // 論理ツリーの上位のDataContextを参照しに行くようになる
}
private void UserControl_Loaded(object sender, RoutedEventArgs e)
{
    NumericUpDown ctrl = sender as NumericUpDown;
    var vm = this.Root.DataContext as ViewModels.NumericUpDownViewModel;
    // 略
}

引用返信 編集キー/
■88781 / inTopicNo.6)  Re[5]: DataGridのセルをUserControlにする
□投稿者/ りんく (5回)-(2018/09/26(Wed) 11:22:52)
No88780 (Hongliang さん) に返信
ご返信ありがとうございます。

ご返信いただいたコードで期待した動作を確認できました。
コードを見て見当違いなことをしていたのだとわかり
まだまだ勉強が足りないなと実感しました。
本当にありがとうございました。

解決済み
引用返信 編集キー/

このトピックをツリーで一括表示


トピック内ページ移動 / << 0 >>

このトピックに書きこむ