Как постоянно менять цвет ListBox SelectedItem после выбора - PullRequest
0 голосов
/ 15 марта 2020

У меня есть список, который загружает его элементы с цветом переднего плана, установленным на красный. Я хотел бы сделать следующее: после выбора элемента мышью измените цвет переднего плана SelectedItem на черный, но сделайте это изменение постоянным, чтобы после отмены выбора элемента цвет оставался черным. Между прочим, я хочу реализовать это как способ показа «прочитанных элементов» пользователю.

По сути, я хочу что-то вроде реализации общего триггера свойства, как показано в коде ниже, но не иметь стиль, возвращаемый после отмены выбора , Я также играл с триггерами событий без особой удачи.

        <ListBox.ItemContainerStyle>
            <Style TargetType="ListBoxItem">
                <Style.Triggers>
                    <Trigger Property="IsSelected" Value="True" >
                        <Setter Property="Foreground" Value="Black" />   //make this persist after deselection
                    </Trigger>
                </Style.Triggers>
            </Style>                
        </ListBox.ItemContainerStyle>

Заранее спасибо!

Ответы [ 2 ]

1 голос
/ 15 марта 2020

Вы можете анимировать свойство Foreground:

<ListBox>
  <ListBox.ItemContainerStyle>
    <Style TargetType="ListBoxItem">
      <Setter Property="Foreground" Value="Red" />

      <Style.Triggers>
        <Trigger Property="IsSelected" Value="True">
          <Trigger.EnterActions>
            <BeginStoryboard>
              <Storyboard>
                <ColorAnimation Storyboard.TargetProperty="(ListBoxItem.Foreground).(SolidColorBrush.Color)"
                                To="Black" />
              </Storyboard>
            </BeginStoryboard>
          </Trigger.EnterActions>
        </Trigger>
      </Style.Triggers>
    </Style>
  </ListBox.ItemContainerStyle>
</ListBox>

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

В зависимости от ваших требований вы можете переопределить ListBoxItem.Template и связать ToggleButton.IsChecked с IsMarkedAsRead или использовать Button, который использует ICommand для установки свойства IsMarkedAsRead. Существует много решений, например, реализация присоединенного поведения.

Следующие примеры переопределяют ListBoxItem.Template, чтобы превратить ListBoxItem в Button. Теперь при нажатии элемента свойство IsMarkedAsRead модели данных устанавливается на true:

Модель данных
(см. Документы Microsoft: Шаблоны - WPF Приложения с шаблоном проектирования Model-View-ViewModel для примера реализации RelayCommand.)

public class Notification : INotifyPropertyChanged
{    
  public string Text { get; set; }
  public ICommand MarkAsReadCommand => new RelayCommand(() => this.IsMarkedAsRead = true);
  public ICommand MarkAsUnreadCommand => new RelayCommand(() => this.IsMarkedAsRead = false);
  private bool isMarkedAsRead;

  public bool IsMarkedAsRead
  {
    get => this.isMarkedAsRead;
    set
    {
      this.isMarkedAsRead = value;
      OnPropertyChanged();
    }
  }

  #region INotifyPropertyChanged

  public event PropertyChangedEventHandler PropertyChanged;

  protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
  {
    this.PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
  }

  #endregion
}

ListBox

<ListBox ItemsSource="{Binding Notifications}">
  <ListBox.ItemContainerStyle>
    <Style TargetType="ListBoxItem">
      <Setter Property="Template">
        <Setter.Value>
          <ControlTemplate TargetType="ListBoxItem">
            <Border Background="{TemplateBinding Background}">
              <Button x:Name="ContentPresenter"
                      ContentTemplate="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType=ListBox}, Path=ItemTemplate}"
                      Content="{TemplateBinding Content}"
                      Command="{Binding MarkAsReadCommand}"
                      Foreground="Red">
                <Button.Template>
                  <ControlTemplate TargetType="Button">
                    <Border>
                      <ContentPresenter />
                    </Border>
                  </ControlTemplate>
                </Button.Template>
              </Button>
            </Border>
            <ControlTemplate.Triggers>
              <DataTrigger Binding="{Binding IsMarkedAsRead}" Value="True">
                <Setter TargetName="ContentPresenter" Property="Foreground" Value="Green" />
              </DataTrigger>
            </ControlTemplate.Triggers>
          </ControlTemplate>
        </Setter.Value>
      </Setter>
    </Style>
  </ListBox.ItemContainerStyle>

  <ListBox.ItemTemplate>
    <DataTemplate DataType="{x:Type Notification}">
      <TextBlock Text="{Binding Text}"/>
    </DataTemplate>
  </ListBox.ItemTemplate>
</ListBox>
0 голосов
/ 17 марта 2020

Большое спасибо @BionicCode за исчерпывающий ответ. Я закончил тем, что пошел с другим решением, которое может или не может быть хорошим соглашением; Я любитель.

Во-первых, мне не нужна сбор данных / настойчивость.

Что касается решения для модели данных и переопределения ListBoxItem.Template, я использую предопределенный класс 'SyndicationItem' в качестве класса данных (мое приложение - Rss Reader). Чтобы реализовать ваше решение для модели данных, я могу взломать неиспользуемое свойство SyndicationItem или использовать наследование SyndicationItem для пользовательского класса (я полагаю, это наиболее профессиональный способ?)

Моя полная модель данных выглядит следующим образом:

ObservableCollection >>> CollectionViewSource >>> ListBox.

В любом случае, я использовал простой код, который в то время был не таким простым:

Сначала XAML :

 <Window.Resources>
        <CollectionViewSource x:Key="fooCollectionViewSource" Source="{Binding fooObservableCollection}" >
            <CollectionViewSource.SortDescriptions>
                <scm:SortDescription PropertyName="PublishDate" Direction="Descending" />
            </CollectionViewSource.SortDescriptions>
        </CollectionViewSource>
        <Style x:Key="DeselectedTemplate" TargetType="{x:Type ListBoxItem}">
            <Setter Property="Foreground" Value="Gray" />
        </Style>
    </Window.Resources>

<ListBox x:Name="LB1" ItemsSource="{Binding Source={StaticResource fooCollectionViewSource}}"   HorizontalContentAlignment="Stretch"  Margin="0,0,0,121" ScrollViewer.HorizontalScrollBarVisibility="Disabled" >

    <ListBox.ItemTemplate>
        <DataTemplate>
            <Grid>
                <Grid.ColumnDefinitions>
                    <ColumnDefinition Width="*"  />
                    <ColumnDefinition Width="80" />
                </Grid.ColumnDefinitions>

                <TextBlock MouseDown="TextBlock_MouseDown"  Grid.Column="0" Text="{Binding Path=Title.Text}" TextWrapping="Wrap" FontWeight="Bold"  />
                <TextBlock Grid.Column="1" HorizontalAlignment="Right" TextAlignment="Center" FontSize="11" FontWeight="SemiBold" 
                    Text="{Binding Path=PublishDate.LocalDateTime, StringFormat='{}{0:d MMM,  HH:mm}'}"/>
            </Grid>
        </DataTemplate>
    </ListBox.ItemTemplate>
</ListBox>

Теперь код позади:

Решение 1: это применяет новый стиль, когда listboxitem не выбран. Больше не используется, поэтому событие LB1_SelectionChanged отсутствует в XAML.

        private void LB1_SelectionChanged(object sender, SelectionChangedEventArgs e)
        {
            if (e.RemovedItems.Count != 0)

            {
                foreach (var lbItem in e.RemovedItems)
                {                                  
                    //get reference to source listbox item.  This was a pain.
                    int intDeselectedItem = LB1.Items.IndexOf(lbItem);
                    ListBoxItem lbi = (ListBoxItem)LB1.ItemContainerGenerator.ContainerFromIndex(intDeselectedItem);

                    /*apply style. Initially, instead of applying a style, I used mylistboxitem.Foreground = Brushes.Gray to set the text color.
                    Howver I noticed that if I scrolled the ListBox to the bottom, the text color would revert to the XAML default style in my XAML.
                    I assume this is because of refreshes / redraws (whichever the correct term).  Applying a new style resolved.*/ 
                    Style style = this.FindResource("DeselectedTemplate") as Style;
                    lbi.Style = style;
                }
            }
        }

Решение 2. Решение, которое я использовал. Происходит при SelectedItem = true, тот же эффект, что и ваше первое предложение.

       private void TextBlock_MouseDown(object sender, MouseButtonEventArgs e)
        {
            TextBlock tb = e.Source as TextBlock;
            tb.Foreground = Brushes.Gray;

        }

...