Подтвердить что ты не робот

Выделение ячеек в WPF DataGrid при изменении значения привязки

У меня есть DataGrid, который обновляет свои данные фоновым процессом каждые 15 секунд. Если какая-либо из данных изменяется, я хочу запустить анимацию, которая выделяет ячейку с измененным значением в желтом цвете, а затем исчезает до белого. Я вроде как работаю, делая следующее:

Я создал стиль с триггером события на Binding.TargetUpdated

<Style x:Key="ChangedCellStyle" TargetType="DataGridCell">
    <Style.Triggers>
        <EventTrigger RoutedEvent="Binding.TargetUpdated">
            <BeginStoryboard>
                <Storyboard>
                    <ColorAnimation Duration="00:00:15"
                        Storyboard.TargetProperty=
                            "(DataGridCell.Background).(SolidColorBrush.Color)" 
                        From="Yellow" To="Transparent" />
                </Storyboard>
            </BeginStoryboard>
        </EventTrigger>
    </Style.Triggers>
</Style>

И затем применил его к столбцам, которые я хотел бы выделить, если значение изменилось

<DataGridTextColumn Header="Status" 
    Binding="{Binding Path=Status, NotifyOnTargetUpdated=True}" 
    CellStyle="{StaticResource ChangedCellStyle}" />

Если значение поля состояния в базе данных изменяется, ячейка выделяется желтым точно так же, как я хочу. Но есть несколько проблем.

Сначала, когда сетка данных изначально загружена, весь столбец выделяется желтым цветом. Это имеет смысл, потому что все значения загружаются в первый раз, поэтому вы ожидаете, что TargetUpdated будет запущен. Я уверен, что есть способ остановить это, но это относительно небольшая точка.

Реальная проблема заключается в том, что весь столбец выделяется желтым, если сетка сортируется или фильтруется каким-либо образом. Наверное, я не понимаю, почему сортировка приведет к тому, что TargetUpdated будет запущен, поскольку данные не изменились, так же, как это отображается.

Итак, мой вопрос: (1) как я могу остановить это поведение при начальной загрузке и сортировке/фильтрации, и (2) я нахожусь на правильном пути, и это даже хороший способ сделать это? Я должен упомянуть, что это MVVM.

4b9b3361

Ответ 1

Так как TargetUpdated - это действительно только событие, основанное на обновлении UI. Не имеет значения, как происходит обновление. Пока сортировка всех DataGridCells остается в их местах, в них изменяются только данные в соответствии с результатом сортировки, поэтому увеличивается TargetUpdated. поэтому мы должны зависеть от уровня данных приложения WPF. Чтобы достичь этого, я reset привязку DataGridCell на основе переменной такого рода трассировки, если обновление происходит на уровне данных.

XAML:

<Window.Resources>
    <Style x:Key="ChangedCellStyle" TargetType="DataGridCell">
        <Setter Property="Template">
            <Setter.Value>
                <ControlTemplate TargetType="DataGridCell">
                    <ControlTemplate.Triggers>
                        <EventTrigger RoutedEvent="Binding.TargetUpdated">
                            <BeginStoryboard>
                                <Storyboard>
                                    <ColorAnimation Duration="00:00:04" Storyboard.TargetName="myTxt"
                                        Storyboard.TargetProperty="(DataGridCell.Background).(SolidColorBrush.Color)" 
                                        From="Red" To="Transparent" />
                                </Storyboard>
                            </BeginStoryboard>
                        </EventTrigger>                           
                    </ControlTemplate.Triggers>

                    <TextBox HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Background="Transparent"
                             Name="myTxt" >
                        <TextBox.Style>
                            <Style TargetType="TextBox">
                                <Style.Triggers>
                                    <DataTrigger Binding="{Binding RelativeSource={RelativeSource Mode=TemplatedParent},Path=DataContext.SourceUpdating}" Value="True">
                                        <Setter Property="Text" Value="{Binding RelativeSource={RelativeSource Mode=TemplatedParent},Path=Content.Text,NotifyOnSourceUpdated=True,NotifyOnTargetUpdated=True}" />
                                    </DataTrigger>
                                    <DataTrigger Binding="{Binding RelativeSource={RelativeSource Mode=TemplatedParent},Path=DataContext.SourceUpdating}" Value="False">
                                        <Setter Property="Text" Value="{Binding RelativeSource={RelativeSource Mode=TemplatedParent},Path=Content.Text}" />                                            
                                    </DataTrigger>                                       
                                </Style.Triggers>                                    
                            </Style>
                        </TextBox.Style>
                    </TextBox>
                </ControlTemplate>
            </Setter.Value>
        </Setter>
    </Style>
</Window.Resources>

<StackPanel Orientation="Vertical">
    <DataGrid ItemsSource="{Binding list}" CellStyle="{StaticResource ChangedCellStyle}" AutoGenerateColumns="False"
              Name="myGrid"  >
        <DataGrid.Columns>
            <DataGridTextColumn Header="Name" Binding="{Binding Name}" />
            <DataGridTextColumn Header="ID" Binding="{Binding Id}" />
        </DataGrid.Columns>
    </DataGrid>
    <Button Content="Change Values" Click="Button_Click" />
</StackPanel>

Код позади (объект DataContext окна):

 public MainWindow()
    {
        list = new ObservableCollection<MyClass>();
        list.Add(new MyClass() { Id = 1, Name = "aa" });
        list.Add(new MyClass() { Id = 2, Name = "bb" });
        list.Add(new MyClass() { Id = 3, Name = "cc" });
        list.Add(new MyClass() { Id = 4, Name = "dd" });
        list.Add(new MyClass() { Id = 5, Name = "ee" });
        list.Add(new MyClass() { Id = 6, Name = "ff" });   
        InitializeComponent();
    }

    private ObservableCollection<MyClass> _list;
    public ObservableCollection<MyClass> list
    {
        get{ return _list; }
        set{   
            _list = value;
            updateProperty("list");
        }
    }

    Random r = new Random(0);
    private void Button_Click(object sender, RoutedEventArgs e)
    {

        int id = (int)r.Next(6);
        list[id].Id += 1;
        int name = (int)r.Next(6);
        list[name].Name = "update " + r.Next(20000);
    }

Свойство Model: SourceUpdating имеет значение true (которое устанавливает привязку для уведомления TargetUpdate через DataTrigger), когда какое-либо уведомление выполняется для MyClass в updateProperty() и после обновления уведомляется UI, SourceUpdating установлено значение false (которое затем reset связывает, чтобы не уведомить TargetUpdate через DataTrigger).

public class MyClass : INotifyPropertyChanged
{
    private string name;
    public string Name
    {
        get { return name; }
        set { 
            name = value;updateProperty("Name");
        }
    }

    private int id;
    public int Id
    {
        get { return id; }
        set 
        { 
            id = value;updateProperty("Id");
        }
    }

    //the vaiable must set to ture when update in this calss is ion progress
    private bool sourceUpdating;
    public bool SourceUpdating
    {
        get { return sourceUpdating; }
        set 
        { 
            sourceUpdating = value;updateProperty("SourceUpdating");
        }
    }        

    public event PropertyChangedEventHandler PropertyChanged;
    public void updateProperty(string name)
    {
        if (name == "SourceUpdating")
        {
            if (PropertyChanged != null)
            {
                PropertyChanged(this, new PropertyChangedEventArgs(name));
            }
        }
        else
        {
            SourceUpdating = true;               
            if (PropertyChanged != null)
            {
                PropertyChanged(this, new PropertyChangedEventArgs(name));
            }               
           SourceUpdating = false;                
        }
    }

}

Выходы:

Два одновременных обновления/кнопка нажимаются один раз:

update1

Многие одновременные обновления/кнопка нажимаются много раз:

update2

SO после обновления, когда сортировка или фильтрация происходит, привязки знают, что ей не нужно вызывать TargetUpdatedмероприятие. Только когда выполняется обновление коллекции источников привязка reset для вызова события TargetUpdated. Кроме того, проблема с начальной окраской также обрабатывается этим.

Однако, поскольку логика все еще имеет какие-то особенности, так как для редактора TextBox логика основана на большей сложности типов данных и логики пользовательского интерфейса, код станет более сложным и для первоначальной привязки reset вся строка анимируется как TargetUpdated создается для всех ячеек строки.

Ответ 2

Мои идеи для точки (1) состоят в том, чтобы справиться с этим в коде. Один из способов - обработать событие TargetUpdated для DataGridTextColumn и выполнить дополнительную проверку старого значения по сравнению с новым значением и применить стиль, только если значения разные, и, возможно, другим способом было бы создать и удалить привязку программно основанный на разных событиях вашего кода (например, начальная загрузка, обновление и т.д.).

Ответ 3

Я предлагаю использовать OnPropertyChanged для каждого реквизита в вашей модели просмотра и обновлять связанные UIElement (запускать анимацию или что-то еще), поэтому ваша проблема будет решена (при загрузке, сортировке, фильтрации,...), а также пользователи могут видеть, какая ячейка изменена