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

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

新規登録して質問してみよう
ただいま回答率
85.35%
DataGrid

GUIの一種であり、データを表の形式でみることが可能です。

WPF

Windows Presentation Foundation (WPF) は、魅力的な外観のユーザー エクスペリエンスを持つ Windows クライアント アプリケーションを作成するための次世代プレゼンテーション システムです

Q&A

解決済

1回答

4780閲覧

Datagridの変化があったセルのみ背景色を変えたい

98mate

総合スコア2

DataGrid

GUIの一種であり、データを表の形式でみることが可能です。

WPF

Windows Presentation Foundation (WPF) は、魅力的な外観のユーザー エクスペリエンスを持つ Windows クライアント アプリケーションを作成するための次世代プレゼンテーション システムです

1グッド

0クリップ

投稿2021/06/28 02:56

前提・実現したいこと

既存コードから諸変更して標題のような機能を追加したく思います。
・セルに入力した値が初期値から変更されていれば、変更したセルの背景色を黄色に変える。
・更新ボタンを押すと、現在値を初期値として更新する。この際背景色は通常色に戻る。

調べたこと、わからないこと、疑問

アプリでは何度かみたことがありますが、WindowsFormしか扱ったことがなく根本的に何から手を付けたらよいかわかりません。
コード作成者がすでに不在で既存部分も理解できていないため1から学習を始めるのですが、本件はすぐに対応が必要なため一時的にしのぎたいです。

<Style Targettype ="DataGridCell">
<DataTrigger Binding="{Binding 変化フラグ}" Value="true"}、
<Setter Property="Background" Value="Yellow"/>で変化があった場合のセルの背景を変えられそう。
→(疑問)DataTriggerっていつ反映される?
→(疑問)これだと背景を変えるセル(座標)が不明?参考サイトだと元々バインドされたプロパティが変化したときの例。

②CellEditEndingで値比較すれば変化フラグは作れそう。

③②のフラグをバインドする方法と、そもそも変化があったことをXAML側でどうやってセルを認識させられるのかわからない。
→(疑問)Contextでrecordsがバインドされているので変化フラグをどのようにバインドする?

該当のソースコード

XAML

1///元々のコードを抜粋 2<UserControl x:Name="userControl" x:Class="App.Program" 3 xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 4 xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 5 xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 6 xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 7 xmlns:local="clr-namespace:lApp" 8 mc:Ignorable="d" 9 d:DesignHeight="300" d:DesignWidth="500"> 10 <Border Name="RightPain" BorderBrush="Navy" BorderThickness="2" CornerRadius="2" Margin="2,2,2,2"> 11 <Grid MinHeight="300" MinWidth="500"> 12 13 <Grid.RowDefinitions> 14 <RowDefinition /> 15 ... 16 </Grid.RowDefinitions> 17 18 <Grid.ColumnDefinitions> 19 <ColumnDefinition /> 20 .... 21 </Grid.ColumnDefinitions> 22 23 <DataGrid Name="Program" Grid.Row="0" Grid.Column="0" Grid.RowSpan="10" Grid.ColumnSpan="6" Margin="10,5,5,5" HorizontalAlignment="Left" 24 ItemsSource="{Binding Mode=OneWay}" AutoGenerateColumns="False" SelectionChanged="Program_SelectionChanged" IsEnabled="False" CanUserReorderColumns="False" CanUserResizeColumns="False" CanUserResizeRows="False" CanUserSortColumns="False" SelectedCellsChanged ="Program_SelectedCellsChanged" SelectionUnit="CellOrRowHeader" CellEditEnding="Program_CellEditEnding"> 25 <DataGrid.Columns> 26 <DataGridTextColumn Header="{Binding Source={x:Static local:ResourceService.Current},Path=Resources.ProgStep, Mode=OneWay}" Width="50" Binding="{Binding _a}" IsReadOnly="True"/> 27 <DataGridTextColumn Header="{Binding Source={x:Static local:ResourceService.Current},Path=Resources.Prog_a, Mode=OneWay}" Width="95" Binding="{Binding _b}"/> 28 <DataGridTextColumn Header="{Binding Source={x:Static local:ResourceService.Current},Path=Resources.Prog_b, Mode=OneWay}" Width="75" Binding="{Binding _c}"/> 29 <DataGridTextColumn Header="{Binding Source={x:Static local:ResourceService.Current},Path=Resources.Prog_c, Mode=OneWay}" Width="90" Binding="{Binding _d}"/> 30 </DataGrid.Columns> 31 32 <!-- サイトを見ている限りこんな感じで変えられる?--> 33 <DataGrid.CellStyle> 34 <Style TargetType="DataGridCell"> 35 <Style.Triggers> 36 <DataTrigger Binding="{Binding change_flag}" Value="true"> 37 <Setter Property="Background" Value="Yellow"/> 38 </DataTrigger> 39 </Style.Triggers> 40 </Style> 41 </DataGrid.CellStyle> 42 </DataGrid> 43 </Grid> 44 </Border> 45</UserControl>

C#

1/// ※元々のコード抜粋 2namespace App 3{ 4 /// <summary> 5 /// Program.xaml の相互作用ロジック 6 /// </summary> 7 public partial class Program : UserControl 8 { 9 List<Record> records = new List<Record>(); 10 bool _flag = false; 11 MainWindow parent; 12 13 public Program() 14 { 15 InitializeComponent(); 16 17 Programdata.programItem prgItem = new Programdata.programItem(); 18 19 for (int i = 0; i < 32; i++) 20 { 21 records.Add(new Record() 22 { 23 _a = i, 24 _b = prgItem.data_b, 25 _c = prgItem.data_c, 26 _d = prgItem.data_d, 27 }); 28 } 29 Program.DataContext = records; 30 Program.IsEnabled = true; 31 } 32 33 34 private void Program_CellEditEnding(object sender, DataGridCellEditEndingEventArgs e) 35 { 36 if( セル初期値 != content = Int16.Parse(((TextBox)(cellInfo.Column.GetCellContent(cellInfo.Item))).Text) 37 { 38 ._flag = true; 39 }else{ 40 ._flag = false;} 41 } 42 private void Program_SelectedCellsChanged(object sender, SelectedCellsChangedEventArgs e){...} 43 private void Program_SelectionChanged(object sender, SelectionChangedEventArgs e){...} 44 45 class ProgRecordGrp 46 { 47 public int _a { get; set; } 48 public UInt16 _b { get; set; } 49 public Int32 _c { get; set; } 50 public UInt16 _d { get; set; } 51 } 52 53 class Flag 54 { 55 public bool _flag{get;set;} 56 } 57}

試したこと

以下の様なサイトを閲覧
ttps://qiita.com/nkimra/items/0f15cfdb89757adfb25a
ttps://qiita.com/tera1707/items/7591889617efc69fcb95

補足情報(FW/ツールのバージョンなど)

VisualStudio 2015
.NET Franework 4.5

TN8001👍を押しています

気になる質問をクリップする

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

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

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

guest

回答1

0

ベストアンサー

コード作成者がすでに不在で既存部分も理解できていないため1から学習を始めるのですが、本件はすぐに対応が必要なため一時的にしのぎたいです。

本当に何もかもわからない状態だとすると、なかなか厳しいと思いますね。
少なくともテスト用のミニマムなプロジェクトを作って、試されたほうがいいと思います(提示コードはなんだかさっぱりわかりません)

DataGridの仮想化を切っても使える程度の少量の件数であれば、WinForms的な発想もありえます。
具体的にはCellEditEndingcell.Background = Brushes.Yellowのような(あくまでイメージです。実際はこれではダメです)

xml

1<DataGrid 2 EnableColumnVirtualization="False" 3 EnableRowVirtualization="False">

こうしてみてとてもじゃないが使いものにならない(読み込み速度やスクロール・カラムサイズ変更等)なら、このアプローチはすっぱり諦めたほうがいいでしょう(手元では1000件程度でもかなり厳しい)

WPF的には98mateさんの調査通り、おそらくDataTriggerでやるのが真っ当なのでしょう。

→(疑問)DataTriggerっていつ反映される?

やってみたらすぐわかるのでは?(WPFではINotifyPropertyChangedは欠かせないので、必ず押さえておいてください)

→(疑問)これだと背景を変えるセル(座標)が不明?参考サイトだと元々バインドされたプロパティが変化したときの例。

発想が逆です。セルごとに状態が必要ならば、セル個数分プロパティが必要です。

xml

1<Window 2 x:Class="Questions346520.MainWindow" 3 xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 4 xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 5 Width="800" 6 Height="450"> 7 <Window.Resources> 8 <Style x:Key="dirtyStyle" BasedOn="{StaticResource {x:Type DataGridCell}}" TargetType="DataGridCell"> 9 <Setter Property="Background" Value="Yellow" /> 10 </Style> 11 </Window.Resources> 12 <Grid> 13 <Grid.ColumnDefinitions> 14 <ColumnDefinition /> 15 <ColumnDefinition /> 16 </Grid.ColumnDefinitions> 17 18 <GroupBox Header="WinForms的発想"> 19 <DockPanel> 20 <Button Click="Button1_Click" Content="更新" DockPanel.Dock="Top" /> 21 <DataGrid 22 x:Name="dataGrid1" 23 AutoGenerateColumns="False" 24 BeginningEdit="DataGrid1_BeginningEdit" 25 CellEditEnding="DataGrid1_CellEditEnding" 26 EnableColumnVirtualization="False" 27 EnableRowVirtualization="False" 28 ItemsSource="{Binding Records1}" 29 SelectionUnit="CellOrRowHeader"> 30 <DataGrid.Columns> 31 <DataGridTextColumn Binding="{Binding No}" Header="No" IsReadOnly="True" /> 32 <DataGridTextColumn Binding="{Binding UshortValue}" Header="ushort" /> 33 <DataGridTextColumn Binding="{Binding StringValue}" Header="string" /> 34 </DataGrid.Columns> 35 </DataGrid> 36 </DockPanel> 37 </GroupBox> 38 39 <GroupBox Grid.Column="1" Header="DataTrigger"> 40 <DockPanel> 41 <Button Click="Button2_Click" Content="更新" DockPanel.Dock="Top" /> 42 <DataGrid AutoGenerateColumns="False" ItemsSource="{Binding Records2}" SelectionUnit="CellOrRowHeader"> 43 <DataGrid.Columns> 44 <DataGridTextColumn Binding="{Binding No}" Header="No" IsReadOnly="True" /> 45 <DataGridTextColumn Binding="{Binding UshortValue}" Header="ushort"> 46 <DataGridTextColumn.CellStyle> 47 <Style TargetType="DataGridCell"> 48 <Style.Triggers> 49 <DataTrigger Binding="{Binding IsDirtyUshortValue}" Value="True"> 50 <Setter Property="Background" Value="Yellow" /> 51 </DataTrigger> 52 </Style.Triggers> 53 </Style> 54 </DataGridTextColumn.CellStyle> 55 </DataGridTextColumn> 56 <DataGridTextColumn Binding="{Binding StringValue}" Header="string"> 57 <DataGridTextColumn.CellStyle> 58 <Style TargetType="DataGridCell"> 59 <Style.Triggers> 60 <DataTrigger Binding="{Binding IsDirtyStringValue}" Value="True"> 61 <Setter Property="Background" Value="Yellow" /> 62 </DataTrigger> 63 </Style.Triggers> 64 </Style> 65 </DataGridTextColumn.CellStyle> 66 </DataGridTextColumn> 67 </DataGrid.Columns> 68 </DataGrid> 69 </DockPanel> 70 </GroupBox> 71 </Grid> 72</Window>

cs

1using System; 2using System.Collections.Generic; 3using System.ComponentModel; 4using System.Linq; 5using System.Runtime.CompilerServices; 6using System.Windows; 7using System.Windows.Controls; 8using System.Windows.Media; 9 10namespace Questions346520 11{ 12 public partial class MainWindow : Window 13 { 14 public List<Record1> Records1 { get; } = new List<Record1>(); 15 public List<Record2> Records2 { get; } = new List<Record2>(); 16 17 private bool isDirty1; 18 private string oldValue; 19 private Style defaultStyle; 20 private Style dirtyStyle; 21 22 public MainWindow() 23 { 24 InitializeComponent(); 25 DataContext = this; 26 27 defaultStyle = FindResource(typeof(DataGridCell)) as Style; 28 dirtyStyle = FindResource("dirtyStyle") as Style; 29 30 for (var i = 0; i < 1000; i++) 31 { 32 Records1.Add(new Record1() 33 { 34 No = i, 35 UshortValue = (ushort)(i * 10), 36 StringValue = (i * 100).ToString(), 37 }); 38 // ↑↓片方ずつコメント化してパフォーマンスチェック 39 Records2.Add(new Record2() 40 { 41 No = i, 42 UshortValue = (ushort)(i * 10), 43 StringValue = (i * 100).ToString(), 44 IsDirtyUshortValue = false, 45 IsDirtyStringValue = false, 46 }); 47 } 48 } 49 50 private void DataGrid1_BeginningEdit(object sender, DataGridBeginningEditEventArgs e) 51 { 52 if (e.EditingEventArgs.Source is TextBlock textBlock) 53 oldValue = textBlock.Text; 54 55 if (e.EditingEventArgs.Source is DataGridCell cell) 56 oldValue = (cell.Content as TextBlock).Text; 57 } 58 59 private void DataGrid1_CellEditEnding(object sender, DataGridCellEditEndingEventArgs e) 60 { 61 if (e.EditAction != DataGridEditAction.Commit) 62 { 63 oldValue = null; 64 return; 65 } 66 67 var textbox = e.EditingElement as TextBox; 68 var newValue = textbox.Text; 69 if (oldValue == newValue) 70 { 71 oldValue = null; 72 return; 73 } 74 75 var cell = textbox.Parent as DataGridCell; 76 cell.Style = dirtyStyle; 77 isDirty1 = true; 78 } 79 80 private void Button1_Click(object sender, RoutedEventArgs e) 81 { 82 if (!isDirty1) return; 83 84 foreach (var cell in dataGrid1.Descendants<DataGridCell>()) 85 cell.Style = defaultStyle; 86 87 isDirty1 = false; 88 } 89 90 private void Button2_Click(object sender, RoutedEventArgs e) 91 { 92 if (!Record2.IsDirty2) return; 93 94 foreach (var record2 in Records2) 95 { 96 record2.IsDirtyUshortValue = false; 97 record2.IsDirtyStringValue = false; 98 } 99 Record2.IsDirty2 = false; 100 } 101 102 103 public class Record1 104 { 105 public int No { get; set; } 106 public ushort UshortValue { get; set; } 107 public string StringValue { get; set; } 108 } 109 110 public class Record2 : Observable 111 { 112 // ダサいがこうするしかないか? 113 public static bool IsDirty2; 114 115 public int No { get; set; } 116 117 public ushort UshortValue 118 { 119 get => _UshortValue; 120 set 121 { 122 if (_UshortValue != value) 123 { 124 _UshortValue = value; 125 IsDirtyUshortValue = true; 126 } 127 } 128 } 129 private ushort _UshortValue; 130 131 // ValueはPropertyChangedを発砲する必要はないが、↑は面倒なので使っちゃうw 132 public string StringValue 133 { 134 get => _StringValue; 135 set { if (SetProperty(ref _StringValue, value)) IsDirtyStringValue = true; } 136 } 137 private string _StringValue; 138 139 // こちらはコードから変更されるため、PropertyChangedを発砲しないとViewに反映されない 140 public bool IsDirtyUshortValue 141 { 142 get => _IsDirtyUshortValue; 143 set { if (SetProperty(ref _IsDirtyUshortValue, value)) IsDirty2 = true; } 144 } 145 private bool _IsDirtyUshortValue; 146 147 public bool IsDirtyStringValue 148 { 149 get => _IsDirtyStringValue; 150 set { if (SetProperty(ref _IsDirtyStringValue, value)) IsDirty2 = true; } 151 } 152 private bool _IsDirtyStringValue; 153 } 154 } 155 156 // [VisualTreeの子孫要素を取得する - xin9le.net](https://blog.xin9le.net/entry/2013/10/29/222336) 157 static class DependencyObjectExtensions 158 { 159 public static IEnumerable<DependencyObject> Children(this DependencyObject obj) 160 { 161 if (obj == null) throw new ArgumentNullException(nameof(obj)); 162 var count = VisualTreeHelper.GetChildrenCount(obj); 163 if (count == 0) yield break; 164 for (var i = 0; i < count; i++) 165 { 166 var child = VisualTreeHelper.GetChild(obj, i); 167 if (child != null) yield return child; 168 } 169 } 170 public static IEnumerable<DependencyObject> Descendants(this DependencyObject obj) 171 { 172 if (obj == null) throw new ArgumentNullException(nameof(obj)); 173 foreach (var child in obj.Children()) 174 { 175 yield return child; 176 foreach (var grandChild in child.Descendants()) 177 yield return grandChild; 178 } 179 } 180 public static IEnumerable<T> Descendants<T>(this DependencyObject obj) where T : DependencyObject => obj.Descendants().OfType<T>(); 181 } 182 183 public class Observable : INotifyPropertyChanged 184 { 185 public event PropertyChangedEventHandler PropertyChanged; 186 protected bool SetProperty<T>(ref T storage, T value, [CallerMemberName] string propertyName = null) 187 { 188 if (Equals(storage, value)) return false; 189 storage = value; 190 OnPropertyChanged(propertyName); 191 return true; 192 } 193 protected void OnPropertyChanged(string propertyName) => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); 194 } 195}

「WinForms的発想」のほうを一時しのぎのつもりで書きましたが、かえって難しいしトータル行数も大差ない気がします^^;
アプリ画像


行ごとの判定でよければDataTableをソースにすれば、簡単にできそうな気はします(おそらく「それじゃダメ」と言われそうなので特に調べていませんが^^;

投稿2021/06/28 10:03

編集2023/08/14 10:10
TN8001

総合スコア9862

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

98mate

2021/06/29 02:38

ご丁寧に回答いただきましてありがとうございます。何が分かっていなくて、何が足りてないか焦っていたところが少し見えてきました。 記載頂いたコードを参考にさせていただき、当初の目的を達成することができました。 INotifyPropertyChangedがミソの様なのでしっかりキャッチアップしたいと思います。
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

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

ただいまの回答率
85.35%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問