次の方法で共有


WPFのデザインの継承や二重指定について 

質問

2013年6月28日金曜日 2:42

Styleで外観を設定して、ItemContainerStyleで、他のデータグリッドにはない特徴を設定しようとしたのですが、うまく動作せず困っています。

ベタに書き込んだItemContainerStyleで指定したプロパティのスタイルは、Styleのリソースディクショナリから読みだした設定よりも優先されて上書きするように設定するのではないかと思ったのですが、考え方が間違っているのでしょうか?

雛形のModanスタイルを継承してつかうBasedOnプロパティを使用するのが正解になるのでしょうか。

気にしているのは、ItemContainerStyleで追加で指定したスタイルは、このグリッドでしか使用しないのでリソースにするのは無駄なのではないかと考えました。

<DataGrid Grid.Row="1" Style="{StaticResource Modan}" ItemsSource="{Binding InOuts}" AutoGenerateColumns="False" IsReadOnly="True" SelectionMode="Single" VerticalContentAlignment="Center">
    <DataGrid.ItemContainerStyle>
        <Style TargetType="DataGridRow">
            <Style.Triggers>
                <DataTrigger Binding="{Binding isReservation}" Value="true">
                    <Setter Property="Background" Value="#F69679" />
                </DataTrigger>
            </Style.Triggers>
        </Style>
    </DataGrid.ItemContainerStyle>
    <DataGrid.Columns>
        <DataGridTextColumn Width="auto" Header="入/出" Binding="{Binding inout}" />
        <DataGridCheckBoxColumn Header="引合" Binding="{Binding isReservation, Mode=OneWay}" />
        <DataGridTextColumn Width="1*" Header="備考" Binding="{Binding memo}" />
        <DataGridTemplateColumn Header="" Width="100">
            <DataGridTemplateColumn.CellTemplate>
                <DataTemplate>
                    <Button Content="選択" Command="{Binding DataContext.OnSelectView, RelativeSource={RelativeSource AncestorType={x:Type DataGrid}}}" CommandParameter="{Binding SelectedItem, RelativeSource={RelativeSource AncestorType={x:Type DataGrid}}}" Margin="-1" />
                </DataTemplate>
            </DataGridTemplateColumn.CellTemplate>
        </DataGridTemplateColumn>
    </DataGrid.Columns>
</DataGrid>

環境

windows 7 pro x32

VisualStudioExpress2012 for windows desktop

すべての返信 (13)

2013年7月1日月曜日 23:31 ✅回答済み | 1 票

どこか別の場所で定義されているStyleを上書きするために、ベタにStyleを書きこむという理解がはあっていますが、その場合の動作は既存のStyleの設定を新しいものに置き換えるという動作になります。どこか別の場所で定義されてるStyleのプロパティの"一部"を上書きしたい場合は、既存のStyleをBaseOnをに指定した新たなStyleを作るという方法をとります。 恐らく今回の場合は、ItemContainerStyleの中のStyleにBaseOn="{StaticResource ベースにしたいスタイル名}"を追加することでご希望の動作になると思うのですがどうでしょうか。気にされている、このDataGridでしか利用しないものをリソースに定義するのは無駄なのではという件についてですがBaseOnを設定したStyleだからといって必ずしもリソースに定義する必要はないです。

かずき Blog:http://d.hatena.ne.jp/okazuki/


2013年7月5日金曜日 4:00 ✅回答済み | 1 票

やはり継承した内容に問題があるように感じていますが、トリガ内容が別なので当たっているのでしょうか?
当てているプロパティはどちらもBackgroundなのが、問題なのかもしれませんが不思議な感じです。

まず Background は今回の問題とは関係ないと思われます。

以前はDataGridに対してスタイルを適用すれば、RowとCellに対しても同時にスタイルを設定できていたと思います。
それが変更後には、Cellは変更出来るけど、Rowは変更できなくなるのは微妙な感じがしています。
他のDataGridは特にRowStyleにこういった効果を追加するわけではないので、全ての他のデータグリッドにもRowStyledだけ別途指定するようになってしまい、スタイルを分離させるとこのままではデメリットであるように思います。
(CellStyleは指定しなくてもいいというのが、不自然な感じももっています)

前のサンプルの例では省略しているだけで、DataGrid のスタイルの中で

<Setter Property="RowStyle" Value="{StaticResource rowStyle}" />       

と定義しても問題ありません。

また、DataGridのスタイルの中に
<Setter Property="RowStyle" Value="{StaticResource rowStyle}" />
を予め入れておくと、やはりベタ書きの部分はやはり消えてしまいました。

要はいずれにしても、汎用スタイルで定義した Grid の行スタイルを維持したまま、個別のグリッドで行スタイルを継承して変更したいなら、独自にリソースを用意しとく必要があります。

以下、改良したXAMLです。XAML内のコメントをご覧いただけば、ある程度意図は汲んで頂けるものと思います。

<!-- 注!これはアプリケーションのリソースです -->
<Application.Resources>
    <!-- Application リソースに行スタイルを定義する -->
    <Style TargetType="DataGridRow" x:Key="rowStyle">
        <Style.Triggers>
            <Trigger Property="IsMouseOver" Value="True">
                <Setter Property="Background" Value="Lavender" />
            </Trigger>
        </Style.Triggers>
    </Style>

    <Style TargetType="DataGrid">
        <!-- Application の全 DataGrid にリソースで定義した行スタイルを反映させる -->
        <Setter Property="RowStyle" Value="{StaticResource rowStyle}" />
    </Style>
</Application.Resources>


<!-- この DataGrid はアプリケーションのスタイルが反映されている -->
<DataGrid Grid.Row="1" ItemsSource="{Binding InOuts}" 
          AutoGenerateColumns="False" IsReadOnly="True" 
          SelectionMode="Single" VerticalContentAlignment="Center">
    <DataGrid.RowStyle>
        <!-- この DataGrid は行スタイルを継承して設定する -->
        <Style TargetType="DataGridRow" BasedOn="{StaticResource rowStyle}" >
            <Style.Triggers>
                <DataTrigger Binding="{Binding IsReservation}" Value="true">
                    <Setter Property="Background" Value="#F69679" />
                </DataTrigger>
            </Style.Triggers>
        </Style>
    </DataGrid.RowStyle>
    <DataGrid.Columns>
        <!-- 省略 -->
    </DataGrid.Columns>
</DataGrid>

ひらぽん http://d.hatena.ne.jp/hilapon/


2013年7月5日金曜日 4:21 ✅回答済み | 1 票

余談ですが、DataGrid のカスタマイズに関しては、やはりある程度構造について知る必要がありそうです。以下参考になりそうな記事を見つけました。

http://blog.smoura.com/wpf-toolkit-datagrid-part-ii-custom-styling/

この辺りの情報って、もっと日本語のリソースがあればいいのでしょうが、検索してもあまり見つからないですね。某社のコンポーネントならかなり詳しい解説があるのですが、MSさんにも頑張ってもらいたいです。

http://help.jp.infragistics.com/Help/NetAdvantage/WPF/2012.1/CLR4.0/html/xamData_Terms_Presenters.html

でも、DataGrid 使ってる案件って実は少ないのかもしれません。私もベンダーさんのデータグリッドを使ってるので、DataGrid はあまり詳しくないです(苦笑

ひらぽん http://d.hatena.ne.jp/hilapon/


2013年6月28日金曜日 7:49

「うまく動作せず困っている」 なら、期待している動作と現状がどう違うのか、具体的に上げた方が回答が付きやすいと思います。

このXAMLの場合、DataTrigger でレコード行の Background を #F69679 に設定したいけど、 isReservation の値が true なのに反映されない、という解釈でよろしいですか?

ひらぽん http://d.hatena.ne.jp/hilapon/


2013年6月28日金曜日 8:17

ベタに書き込んだItemContainerStyleで指定したプロパティのスタイルは、Styleのリソースディクショナリから読みだした設定よりも優先されて上書きするように設定するのではないかと思ったのですが、考え方が間違っているのでしょうか?

それは問題ないと思います。
基本的に Application.xaml で定義したスタイルより、リソースで定義したスタイルが優先され、
リソースのスタイルより、直接要素に定義したスタイルが優先されます。スタイルを継承して使いたいなら BasedOn を使います。

あと先ほどの回答で DataTrigger が意図通りに動作しないのではと、私の推測を上げましたが、試しに組んでみたところ DataGrid.ItemContainerStyle の DataTrigger はきちんと動作しました。こんな感じです。

以下、サンプルを上げておきます。MVVM のフレームワークは Livet を使いました。

まずモデル

using Livet;

namespace LivetWPFApplication1.Models {
    public class InOut : NotificationObject {
        #region Inout変更通知プロパティ
        private string _Inout;
        public string Inout {
            get { return _Inout; }
            set { 
                if (_Inout == value)  return;
                _Inout = value;
                RaisePropertyChanged("Inout");
            }
        }
        #endregion

        #region IsReservation変更通知プロパティ
        private bool _IsReservation;
        public bool IsReservation {
            get { return _IsReservation; }
            set { 
                if (_IsReservation == value) return;
                _IsReservation = value;
                RaisePropertyChanged("IsReservation");
            }
        }
        #endregion

        #region Memo変更通知プロパティ
        private string _Memo;
        public string Memo {
            get { return _Memo; }
            set { 
                if (_Memo == value) return;
                _Memo = value;
                RaisePropertyChanged("Memo");
            }
        }
        #endregion
    }
}

次に ViewModel

using System.Collections.ObjectModel;
using Livet;
using LivetWPFApplication1.Models;

namespace LivetWPFApplication1.ViewModels {
    public class MainWindowViewModel : ViewModel {

        public void Initialize() {
        }

        #region InOuts変更通知プロパティ
        private ObservableCollection<InOut> _InOuts;

        public ObservableCollection<InOut> InOuts {
            get { return _InOuts; }
            set { 
                if (_InOuts == value) return;
                _InOuts = value;
                RaisePropertyChanged("InOuts");
            }
        }
        #endregion

        public MainWindowViewModel() {
            this.InOuts = new ObservableCollection<InOut>();
            this.InOuts.Add(new InOut() { 
                Inout = "野菜", IsReservation = true, Memo = "白菜" 
            });
            this.InOuts.Add(new InOut() {
                Inout = "ジュース", IsReservation = false, Memo = "コカ・コーラ"
            });
            this.InOuts.Add(new InOut() {
                Inout = "肉", IsReservation = true, Memo = "牛肉"
            });
            this.InOuts.Add(new InOut() {
                Inout = "果物", IsReservation = false, Memo = "オレンジ・リンゴ"
            });
        }
    }
}

最後 View です。何かの参考になれば幸いです。

<Window x:Class="LivetWPFApplication1.Views.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
        xmlns:ei="http://schemas.microsoft.com/expression/2010/interactions"
        xmlns:l="http://schemas.livet-mvvm.net/2011/wpf"
        xmlns:v="clr-namespace:LivetWPFApplication1.Views"
        xmlns:vm="clr-namespace:LivetWPFApplication1.ViewModels"
        Title="MainWindow" Height="350" Width="525">
    
    <Window.DataContext>
        <vm:MainWindowViewModel/>
    </Window.DataContext>
    <Grid>
        <DataGrid Grid.Row="1" ItemsSource="{Binding InOuts}" 
                  AutoGenerateColumns="False" IsReadOnly="True" 
                  SelectionMode="Single" VerticalContentAlignment="Center">
            <DataGrid.ItemContainerStyle>
                <Style TargetType="DataGridRow">
                    <Style.Triggers>
                        <DataTrigger Binding="{Binding IsReservation}" Value="true">
                            <Setter Property="Background" Value="#F69679" />
                        </DataTrigger>
                    </Style.Triggers>
                </Style>
            </DataGrid.ItemContainerStyle>
            <DataGrid.Columns>
                <DataGridTextColumn Width="auto" Header="入/出" Binding="{Binding Inout}" />
                <DataGridCheckBoxColumn Header="引合" Binding="{Binding IsReservation, Mode=OneWay}" />
                <DataGridTextColumn Width="1*" Header="備考" Binding="{Binding Memo}" />
                <DataGridTemplateColumn Header="" Width="100">
                    <DataGridTemplateColumn.CellTemplate>
                        <DataTemplate>
                            <Button Content="選択" 
                                    Command="{Binding DataContext.OnSelectView, 
                                        RelativeSource={RelativeSource AncestorType={x:Type DataGrid}}}" 
                                    CommandParameter="{Binding SelectedItem, 
                                        RelativeSource={RelativeSource AncestorType={x:Type DataGrid}}}" 
                                    Margin="-1" />
                        </DataTemplate>
                    </DataGridTemplateColumn.CellTemplate>
                </DataGridTemplateColumn>
            </DataGrid.Columns>
        </DataGrid>     
    </Grid>
</Window>

ひらぽん http://d.hatena.ne.jp/hilapon/


2013年7月4日木曜日 4:15

フォーラム オペレーターの星 睦美です。
めめ さん、こんにちは

ひらぽん さんとかずき_okazuki さんからの返信が参考になるのではないかと思います。
私から[回答としてマーク]させていただきましたが、もし回答の内容に質問がありましたら
遠慮なく[回答としてのマークの解除] をして返信いただければと思います。

今後とも役立つ回答には投稿者から[回答としてマーク] いただければ幸いです。

MSDN フォーラムをよろしくお願いします。

日本マイクロソフト株式会社 フォーラム オペレーター 星 睦美


2013年7月4日木曜日 8:40

返信ありがとうございます。BasedOnの考え方がリソース用と思っていました。間違っていたようです。ありがとうございます。

ただ、試行錯誤してみたのですが、どうもうまく解決できていません。

Styleを外せば、ベタに記述した内容は正しく動作しているようですが、Styleを付与するとStyleは当たりましたが、Styleが変化しなくなるといった感じです。

Styleで設定したトリガー内容と干渉しているのかと疑っています。何か設定の優先順位に問題があるのでしょうか。

<Grid Grid.Row="1" Margin="10,10,10,10">
            <DataGrid Grid.Row="1" Style="{StaticResource Modan}" ItemsSource="{Binding InOuts}" AutoGenerateColumns="False" IsReadOnly="True" SelectionMode="Single" VerticalContentAlignment="Center">
                <i:Interaction.Triggers>
                    <i:EventTrigger EventName="SelectionChanged">
                        <i:InvokeCommandAction Command="{Binding OnSelectionChangedParts}" CommandParameter="{Binding SelectedItem, RelativeSource={RelativeSource AncestorType={x:Type DataGrid}}}" />
                    </i:EventTrigger>
                </i:Interaction.Triggers>
                <DataGrid.ItemContainerStyle>
                    <Style BasedOn="{StaticResource Modan}" TargetType="DataGrid">
                        <Style.Triggers>
                            <DataTrigger Binding="{Binding isReservation}" Value="true">
                                <Setter Property="Background" Value="#F69679" />
                            </DataTrigger>
                        </Style.Triggers>
                    </Style>
                </DataGrid.ItemContainerStyle>
                <DataGrid.Columns>・・・</DataGrid.Columns>

            </DataGrid>
            
        </Grid>

スタイルの定義

<!-- DataGird -->
    <Style x:Key="Modan" TargetType="DataGrid">
        <!-- Make the border and grid lines a little less imposing -->
        <Setter Property="BorderBrush" Value="#DDDDDD" />
        <Setter Property="HorizontalGridLinesBrush" Value="#DDDDDD" />
        <Setter Property="VerticalGridLinesBrush" Value="#DDDDDD" />

        <Setter Property="ColumnHeaderStyle">
            <Setter.Value>
                <Style TargetType="DataGridColumnHeader">
                    <Setter Property="FontWeight" Value="Bold" />
                </Style>
            </Setter.Value>
        </Setter>
        
        <Setter Property="RowStyle">
            <Setter.Value>
                <Style TargetType="DataGridRow">
                    <Style.Triggers>
                        <!-- Highlight a grid row as the mouse passes over -->
                        <Trigger Property="IsMouseOver" Value="True">
                            <Setter Property="Background" Value="Lavender" />
                        </Trigger>
                    </Style.Triggers>
                </Style>
            </Setter.Value>
        </Setter>

        <Setter Property="CellStyle">
            <Setter.Value>
                <Style TargetType="DataGridCell">
                    <Style.Triggers>
                        <!-- Highlight selected rows -->
                        <Trigger Property="IsSelected" Value="True">
                            <Setter Property="Background" Value="Lavender" />
                            <Setter Property="BorderBrush" Value="Lavender" />
                            <Setter Property="Foreground" Value="Black" />
                        </Trigger>
                    </Style.Triggers>
                    <!-- Add some padding around the contents of a cell -->
                    <Setter Property="Padding" Value="4,3,4,3" />
                    <Setter Property="Template">
                        <Setter.Value>
                            <ControlTemplate TargetType="DataGridCell">
                                <Border Padding="{TemplateBinding Padding}" 
                            Background="{TemplateBinding Background}">
                                    <ContentPresenter />
                                </Border>
                            </ControlTemplate>
                        </Setter.Value>
                    </Setter>
                    <Setter Property="FocusVisualStyle">
                        <Setter.Value>
                            <Style TargetType="Control">
                                <Setter Property="BorderBrush" Value="Transparent" />
                            </Style>
                        </Setter.Value>
                    </Setter>
                </Style>
            </Setter.Value>
        </Setter>

    </Style>

2013年7月4日木曜日 8:43

丁寧な回答ありがとうございます。

ベタに記述しているDataTrigger はどうもうまく動いているようです。サンプルありがとうございます。自分でも確認してみました。

Triggerを使っているスタイルを継承したところに問題があるのかもしれないと思っています。


2013年7月4日木曜日 9:42 | 1 票

そもそも DataGrid.ItemContainerStyle のターゲットタイプに DataGrid を指定してるのが問題だと思います。ターゲットタイプは DataGridRow であるべきかと。。。
こんな感じでいかがでしょうか?

<Window.Resources>
    <Style TargetType="DataGridRow" x:Key="rowStyle">
        <Style.Triggers>
            <Trigger Property="IsMouseOver" Value="True">
                <Setter Property="Background" Value="Lavender" />
            </Trigger>
        </Style.Triggers>
    </Style>

    <Style TargetType="DataGridCell" x:Key="cellStyle">
        <Style.Triggers>
            <Trigger Property="IsSelected" Value="True">
                <Setter Property="Background" Value="Lavender" />
                <Setter Property="BorderBrush" Value="Lavender" />
                <Setter Property="Foreground" Value="Black" />
            </Trigger>
        </Style.Triggers>
        <Setter Property="Padding" Value="4,3,4,3" />
        <Setter Property="Template">
            <Setter.Value>
                <ControlTemplate TargetType="DataGridCell">
                    <Border Padding="{TemplateBinding Padding}" 
                            Background="{TemplateBinding Background}">
                        <ContentPresenter />
                    </Border>
                </ControlTemplate>
            </Setter.Value>
        </Setter>
        <Setter Property="FocusVisualStyle">
            <Setter.Value>
                <Style TargetType="Control">
                    <Setter Property="BorderBrush" Value="Transparent" />
                </Style>
            </Setter.Value>
        </Setter>
    </Style>

    <Style x:Key="Modan" TargetType="DataGrid">
        <!-- Make the border and grid lines a little less imposing -->
        <Setter Property="BorderBrush" Value="#DDDDDD" />
        <Setter Property="HorizontalGridLinesBrush" Value="#DDDDDD" />
        <Setter Property="VerticalGridLinesBrush" Value="#DDDDDD" />

        <Setter Property="ColumnHeaderStyle">
            <Setter.Value>
                <Style TargetType="DataGridColumnHeader">
                    <Setter Property="FontWeight" Value="Bold" />
                </Style>
            </Setter.Value>
        </Setter>
        <Setter Property="CellStyle" Value="{StaticResource cellStyle}" />
    </Style>
</Window.Resources>
<Grid>
    <Grid Grid.Row="1" Margin="10,10,10,10">
        <DataGrid Grid.Row="1" Style="{StaticResource Modan}" 
            ItemsSource="{Binding InOuts}" AutoGenerateColumns="False" 
            IsReadOnly="True" SelectionMode="Single" VerticalContentAlignment="Center">
            <DataGrid.ItemContainerStyle>
                <Style TargetType="DataGridRow" BasedOn="{StaticResource rowStyle}">
                    <Style.Triggers>
                        <DataTrigger Binding="{Binding IsReservation}" Value="true">
                            <Setter Property="Background" Value="#F69679" />
                        </DataTrigger>
                    </Style.Triggers>
                </Style>
            </DataGrid.ItemContainerStyle>
            <DataGrid.Columns>
                <DataGridTextColumn Width="auto" Header="入/出" Binding="{Binding Inout}" />
                <DataGridCheckBoxColumn Header="引合" Binding="{Binding IsReservation, Mode=OneWay}" />
                <DataGridTextColumn Width="1*" Header="備考" Binding="{Binding Memo}" />
            </DataGrid.Columns>
        </DataGrid>
    </Grid>
</Grid>

ひらぽん http://d.hatena.ne.jp/hilapon/


2013年7月4日木曜日 9:55

追記です。
たぶん DataGrid とグリッドを構成する各要素の関係につき混乱されてるのかも知れません。
DataGrid と構成する各要素(DataGridRow等)はそれぞれ独立した要素なので、各々独自にリソースを定義し、組み合わせて使うといいかも知れません。

ひらぽん http://d.hatena.ne.jp/hilapon/


2013年7月5日金曜日 0:51

回答ありがとうございます。
サンプルの通り動かしてみたところ、期待の動作をしました。ありがとうございます。

ただ、以前はDataGridに対してスタイルを適用すれば、RowとCellに対しても同時にスタイルを設定できていたと思います。
それが変更後には、Cellは変更出来るけど、Rowは変更できなくなるのは微妙な感じがしています。
他のDataGridは特にRowStyleにこういった効果を追加するわけではないので、全ての他のデータグリッドにもRowStyledだけ別途指定するようになってしまい、スタイルを分離させるとこのままではデメリットであるように思います。
(CellStyleは指定しなくてもいいというのが、不自然な感じももっています)

また、DataGridのスタイルの中に
<Setter Property="RowStyle" Value="{StaticResource rowStyle}" />
を予め入れておくと、やはりベタ書きの部分はやはり消えてしまいました。

やはり継承した内容に問題があるように感じていますが、トリガ内容が別なので当たっているのでしょうか?
当てているプロパティはどちらもBackgroundなのが、問題なのかもしれませんが不思議な感じです。


2013年7月5日金曜日 6:31

返信ありがとうございます。

全てのデザインに予め当ててしまうやり方で解決するのですね。知ってはいましたが、こう使うとは思わず、なるほどと思いました。

これは自分の感性の問題なんだと思いますが、やっぱり予め全てのコンポーネントに基底デザインを当てるのはやってみてちょっと怖いように感じています。簡単なフォントやサイズを当てているだけならよいのですが、トリガーまでとなると今回みたいな事がまた起こりそうな気がして、不安な感じがしてしまいます。

なので、しかたなくModanを2つに分けて、一般用のModanと、このグリット専用のModanBaseに分けておくことにしました。

ModanBaseをBasedOn継承してModanを作り、問題のスタイルを追加する感じです。

リソースとしては微妙な感じですが、妥協点というか折衷案という感じになったのかなぁ、と思います。

DataGirdの構造については、ご指摘のとおり、私はあまり調べておりませんでした。というよりも、こんなややこしいとは思わず、楽観視していたくらいです。参考のURLは土日でゆっくり読んでみたいと思います。

長くなりましたが、なんとか解決しとても助かりました。詳しく教えて頂き、本当にありがとうございました!


2013年7月5日金曜日 7:03

お役に立てたようでよかったです。
かずき_okazuki さんの返信もスタイルとリソースの関係につき、たいへん有用なことを述べておられますので、今後このスレッドを訪れる方の参考になるよう、私の方で回答マークを付けさせて頂きますね。

今後ともフォーラムのご利用、よろしくお願いいたします。

ひらぽん http://d.hatena.ne.jp/hilapon/