Search code examples
wpfbindingdatatrigger

WPF: How to use Style.Triggers


I want to implement (file) Explorer like icon display. The items have date and label.

User should be able to edit the label:

  • Select an item
  • Click on label
  • Label's TextBlock is replaced with TextBox for editing

How to end editing (just for info):

  • Click anywhere outside of the TextBox
  • Press Enter keyboard key (by implementing ICommand?)

1st I tried to set the Visibility of TextBlock and TextBox in code found out it is not the 'right' way to to do. Maybe it is possible to edit item's Label using (Data)Triggers?

I can track the OnClickLabelBlock and set selectedMedia.IsEditing = true; but it does not fire the trigger. Any idea why MediaItem.IsEditing property value change is notifying the DataTrigger? Is it something to do with the order of execution or priority mechanism?

I will pick the answer which guides me to the 'best' architecture to solve it.

Thanks.

XAML:

<Window x:Class="WPFComponents.DailyImages"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:Model="clr-namespace:WPFComponents.Model"
    Title="Media Items" Height="300" Width="300">

<ListView x:Name="_mediaItemList" ItemsSource="{Binding MediaItems}"
          ScrollViewer.HorizontalScrollBarVisibility="Disabled" SelectionMode="Multiple">
    <ListView.ItemContainerStyle>
        <Style TargetType="ListViewItem">
            <Setter Property="IsSelected" Value="{Binding IsSelected}" />
        </Style>
    </ListView.ItemContainerStyle>

    <ListView.ItemTemplate>
        <DataTemplate DataType="Model:MediaItem">

            <Grid Width="80" Margin="4">
                <Grid.RowDefinitions>
                    <RowDefinition></RowDefinition>
                    <RowDefinition></RowDefinition>
                </Grid.RowDefinitions>
                <Image HorizontalAlignment="Center" Stretch="Uniform" Source="{Binding Path=IconPath}" Width="70" />

                <StackPanel Grid.Row="2">
                    <TextBlock Text="{Binding Path=Date}" TextWrapping="Wrap" />

                    <TextBlock x:Name="_labelTextBlock" Text="{Binding Path=Label}" TextWrapping="Wrap"
                               PreviewMouseLeftButtonDown="OnClickLabelBlock">
                    </TextBlock>

                    <TextBox x:Name="_labelTextBox" Text="{Binding Path=Label}" Visibility="Collapsed"
                             TextWrapping="WrapWithOverflow" TextAlignment="Center">
                    </TextBox>
                </StackPanel>
            </Grid>

            <DataTemplate.Triggers>
                <DataTrigger Binding="{Binding IsEditing}" Value="True">
                    <Setter TargetName="_labelTextBlock" Property="Visibility" Value="Collapsed" />
                    <Setter TargetName="_labelTextBox" Property="Visibility" Value="Visible" />
                </DataTrigger>
            </DataTemplate.Triggers>

        </DataTemplate>
    </ListView.ItemTemplate>

    <ListView.ItemsPanel>
        <ItemsPanelTemplate>
            <WrapPanel IsItemsHost="True" VerticalAlignment="Top" />
        </ItemsPanelTemplate>
    </ListView.ItemsPanel>

</ListView>

Source:

public partial class DailyImages
{
    public DailyImages()
    {
        InitializeComponent();

        ViewModel.DailyImages dailyImages = new ViewModel.DailyImages();
        // DailyImages has ObservableCollection<MediaItem> MediaItems property
        _mediaItemList.DataContext = dailyImages;
    }

    private void OnClickLabelBlock(object sender, MouseButtonEventArgs e)
    {
        TextBlock notes = sender as TextBlock;

        if (notes == null)
            return;

        MediaItem selectedMedia = notes.DataContext as MediaItem;

        if (selectedMedia == null)
        {
            // TODO: Throw exception
            return;
        }
        _mediaItemList.SelectedItems.Clear();
        selectedMedia.IsSelected = true;
        selectedMedia.IsEditing = true;
    }

public class MediaItem
{
    public MediaItem()
    {
        IsEditing = false;
        IsSelected = false;
    }

    public DateTime Date { get; set; }
    public string Label { get; set; }
    public string IconPath { get; set; }
    public bool IsEditing { get; set; }
    public bool IsSelected { get; set; }
}

References: Dependency Property Value Precedence

Part II: ListView & File Explorer Like Behaviour


Solution

  • To bind your IsEditing property, WPF has to be notified when it is modified.

    Then you have to implement INotifyPropertyChanged in MediaItem. (Or add dependency properties)

    public class MediaItem : INotifyPropertyChanged
    {
        public MediaItem()
        {
            IsEditing = false;
            IsSelected = false;
        }
    
        // Use the same pattern for Date, Label & IconPath if these value may change after the MediaItem instance has been added to the collection MediaItems.
        public DateTime Date { get; set; }
        public string Label { get; set; }
        public string IconPath { get; set; }
    
        private bool isSelected;
        public bool IsSelected
        {
            get { return isSelected; }
            set
            {
                if (isSelected != value)
                {
                    isSelected = value;
                    OnPropertyChanged("IsSelected");
                }
            }
        }
    
        private bool isEditing;
        public bool IsEditing
        {
            get { return isEditing; }
            set
            {
                if (isEditing != value)
                {
                    isEditing = value;
                    OnPropertyChanged("IsEditing");
                }
            }
        }
    
        #region INotifyPropertyChanged
        public event PropertyChangedEventHandler PropertyChanged;
    
        protected virtual void OnPropertyChanged(string propertyName)
        {
            PropertyChangedEventHandler handler = PropertyChanged;
            if (handler != null)
                handler(this, new PropertyChangedEventArgs(propertyName));
        }
        #endregion
    }
    

    Otherwise, your code is correct.