ListBox DataTrigger for IsSelected == Ложная стрельба по населению? - PullRequest
2 голосов
/ 02 марта 2012

У меня проблема с ListBox, но он включает несколько пользовательских элементов управления. Я дам краткое и длинное описание проблемы.

Короткая версия

У меня есть ListBox, который заполняется несколькими элементами, устанавливая его DataContext. Поведение этих элементов частично связано через триггер со свойством IsSelected. Кажется, что всякий раз, когда ListBox заполняется элементами (DataContext установлен), для свойства IsSelected явно устанавливается значение false для всех элементов в списке, что вызывает срабатывание триггера для каждого элемента. Я ожидал, что для каждого элемента IsSelected по умолчанию установлено значение false, и для каждого элемента не было срабатывания триггера. Это ожидаемое поведение ListBox?

Подробная версия

Вот что я пытаюсь достичь: пользовательский ListBox (SequenceNavigator), содержащий то, что я называю ThumbnailViewer в качестве ListBoxItems. ThumbnailViewer показывает изображение, а также имеет одну (или более) кнопку IconOverlayButtons, наложенную на изображение. IconOverlayButton, как следует из названия, представляет собой кнопку, которая представлена ​​значком (DrawingBrush на прямоугольнике). IconOverlayButton должен отображаться только для выбранного элемента в списке, он должен исчезать и исчезать.

У меня почти все работает, но я столкнулся с проблемой. Чтобы уведомить IconOverlayButton о постепенном или постепенном исчезновении, я установил триггеры в ListBox следующим образом:

                    <DataTrigger Binding="{Binding Path=IsSelected, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type s:SurfaceListBoxItem}}}" Value="True">
                        <Setter TargetName="uiThumbnailViewer" Property="Mode" Value="{x:Static local:DisplayMode.Playback}"/>
                        ...
                    </DataTrigger>
                    <DataTrigger Binding="{Binding Path=IsSelected, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type s:SurfaceListBoxItem}}}" Value="False">
                        ...
                        <Setter TargetName="uiThumbnailViewer" Property="IsEnabled" Value="False"/>
                    </DataTrigger> 

Затем я, в свою очередь, установил ControlTemplate элемента IconOverlayButton, чтобы он реагировал на изменяющиеся значения свойства IsEnabled:

<UserControl x:Class="...UIIconOverlayButton"
         ...>
<Grid>
    <Button>
        <Button.Template>
            <ControlTemplate TargetType="{x:Type Button}">
                <Grid>
                    <Rectangle Opacity="0" x:Name="rctIcon"/>
                </Grid>
                <ControlTemplate.Triggers>
                    <Trigger Property="IsPressed" Value="True">
                        <Setter TargetName="rctIcon" Property="Fill" Value="{Binding Path=ButtonDownBrush, RelativeSource={RelativeSource AncestorType={x:Type local:UIIconOverlayButton}}}" />
                    </Trigger>
                    <Trigger Property="IsPressed" Value="False">
                        <Setter TargetName="rctIcon" Property="Fill" Value="{Binding Path=ButtonUpBrush, RelativeSource={RelativeSource AncestorType={x:Type local:UIIconOverlayButton}}}" />
                    </Trigger>
                    <Trigger Property="IsEnabled" Value="True">
                        <Trigger.EnterActions>
                            <BeginStoryboard>
                                <Storyboard FillBehavior="Stop">
                                    <DoubleAnimation Storyboard.TargetName="rctIcon" Storyboard.TargetProperty="Opacity"
                                 From="0" To="1" Duration="0:0:1" />
                                </Storyboard>
                            </BeginStoryboard>
                        </Trigger.EnterActions>
                        <Trigger.Setters>
                            <Setter TargetName="rctIcon" Property="Opacity" Value="1"/> 
                        </Trigger.Setters>
                        <Trigger.ExitActions>
                            <BeginStoryboard>
                                <Storyboard FillBehavior="Stop">
                                    <DoubleAnimation Storyboard.TargetName="rctIcon" Storyboard.TargetProperty="Opacity"
                                 From="1" To="0" Duration="0:0:0.25" />
                                </Storyboard>
                            </BeginStoryboard>
                        </Trigger.ExitActions>
                    </Trigger>
                </ControlTemplate.Triggers>
            </ControlTemplate>
        </Button.Template>
    </Button>
</Grid>

Это решение на 90% работает. Когда я выбираю ListBoxItem (ThumbnailViewer), IconOverlayButton корректно исчезает, и когда я выбираю другой элемент, предыдущий исчезает, а выбранный исчезает.

ПРОБЛЕМА: Когда заполнен ListBox, кажется, что IsSelected явно установлен в False для каждого элемента (не я, насколько я знаю). Это запускает часть ExitAction объекта DataTrigger IconOverlayButton, в результате чего IconOverlayButton мгновенно появляется и исчезает для каждого элемента в списке. Это, конечно, нежелательное поведение. Я надеялся, что для новых элементов IsSelected будет по умолчанию ложным, поэтому триггер отсутствует.

РЕДАКТИРОВАТЬ: Обновлено название, чтобы быть более точным. Кроме того, я попробовал это с ListBox / ListBoxItem вместо элемента SurfaceListBox / SurfaceListBox, и поведение такое же. Surface SDK здесь не является проблемой.

1 Ответ

3 голосов
/ 03 марта 2012

Вам необходимо установить триггер как MultiDataTrigger и использовать состояние IsLoaded в качестве одного из триггеров.Это предотвратит срабатывание триггера до загрузки элемента управления.Для этого есть несколько ключевых модификаций.

Во-первых, вам нужно изменить этот триггер:

<Trigger Property="IsEnabled" Value="True">

... изменив его на MultiDataTrigger:

<MultiDataTrigger>
    <MultiDataTrigger.Conditions>
        <Condition Binding="{Binding RelativeSource={RelativeSource Self}, Path=IsEnabled}" Value="True" />
        <Condition Binding="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type my:actionButton}}, Path=IsLoaded}" Value="True" />
    </MultiDataTrigger.Conditions>
....

my:actionButton - это UserControl (вы хотите посмотреть его свойство IsLoaded).Я использую MultiDataTrigger специально, потому что IsLoaded не является свойством зависимости и поэтому требует DataTrigger или MultiDataTrigger.

Еще не сделано!Поскольку IsLoaded не является свойством зависимости, вам придется уведомлять пользовательский интерфейс от codebehind при его обновлении.

    public actionButton()
    {
        InitializeComponent();
        this.Loaded += new RoutedEventHandler(actionButton_Loaded);
    }

    void actionButton_Loaded(object sender, RoutedEventArgs e)
    {
        Notify("IsLoaded");
    }

Это должно решить вашу проблему!Как заявление об отказе от ответственности, я не могу гарантировать, что это ЛУЧШЕЕ решение, но я попытался выбрать достаточно чистое.

Редактировать: Уведомить:

    public event PropertyChangedEventHandler PropertyChanged;

    private void Notify(String propertyName)
    {
        if (PropertyChanged != null)
        {
            PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
        }
    }
...