IsSelected的ListBox DataTrigger==填充时错误触发
本文关键字:错误 填充 ListBox DataTrigger IsSelected | 更新日期: 2023-09-27 18:26:33
我有一个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>
然后,我依次设置IconOverlayButton的ControlTemplate,以对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(据我所知,不是我)。这会触发IconOverlayButton的DataTrigger的ExitAction部分,导致IconOverlay按钮暂时出现,并为列表中的每个项目淡出。这当然是不可取的行为。我希望IsSelected对于新项目默认为false,因此没有触发器。
编辑:更新标题以更准确。此外,我用ListBox/ListBoxItem而不是SurfaceListBox/SurfaceListBox项进行了尝试,其行为是相同的。Surface SDK在这里似乎没有问题。
您需要将触发器设置为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
不是依赖属性,因此在更新时必须从codeehind通知UI。
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));
}
}