WPF中如何访问样式内Popup属性并实现TimePicker选中后关闭
问题说明
在WPF项目中使用Xceed Toolkit的TimePicker控件,时间选择ListBox通过Popup展示,由ToggleButton控制开关,且Popup的StaysOpen设为False(点击外部可关闭)。需求是选中时间选项后自动关闭Popup,但遇到以下问题:
- 后台代码无法直接访问App.xaml样式中定义的Popup元素
- 不清楚如何用更简便的XAML方式实现该功能
方法一:XAML纯实现(推荐)
核心思路:利用触发器,在ListBoxItem被选中时,修改ToggleButton的IsChecked状态(Popup的IsOpen已绑定到该状态),从而自动关闭Popup。
修改App.xaml中TimePickerStyle内的ListBox.ItemContainerStyle,在ControlTemplate.Triggers中添加如下触发器:
<Style x:Key="TimePickerStyle"> <Setter Property="Template"> ... <ToggleButton x:Name="_timePickerToggleButton"> ... </ToggleButton> <Popup x:Name="PART_Popupx" AllowsTransparency="True" IsOpen="{Binding IsChecked, ElementName=_timePickerToggleButton}" StaysOpen="False" ToolTip=""> <Border> <Grid> <ScrollViewer> <ListBox x:Name="PART_TimeListItems"> <ListBox.ItemContainerStyle> <Style TargetType="{x:Type ListBoxItem}"> <Setter Property="Padding" Value="4,3" /> <Setter Property="Template"> <Setter.Value> <ControlTemplate TargetType="{x:Type ListBoxItem}"> <!-- 原有模板内容保留 --> <ControlTemplate.Triggers> <!-- 原有触发器保留 --> <!-- 新增:选中项时关闭Popup --> <Trigger Property="IsSelected" Value="True"> <Setter TargetName="_timePickerToggleButton" Property="IsChecked" Value="False" RelativeSource="{RelativeSource AncestorType={x:Type toolkit:TimePicker}}" /> </Trigger> </ControlTemplate.Triggers> </ControlTemplate> </Setter.Value> </Setter> </ListBox.ItemContainerStyle> </ListBox> </ScrollViewer> </Grid> </Border> </Popup>
原理说明
通过RelativeSource找到父级的TimePicker控件,直接修改其模板内ToggleButton的IsChecked状态为False,由于Popup的IsOpen绑定到该状态,会自动关闭。这种方式完全符合WPF样式分离的设计原则,无需后台代码干预。
方法二:后台代码实现(可视化树查找元素)
如果需要通过后台代码实现,核心是通过可视化树遍历,找到TimePicker模板内的ToggleButton或Popup。
步骤1:修改XAML事件绑定
将原TextBoxBase.TextChanged替换为TimePicker的ValueChanged事件(更精准,仅在时间选中时触发):
<toolkit:TimePicker x:Name="tp" Grid.Column="2" Kind="Local" Background="White" Value="{Binding ReminderTime, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" Style="{StaticResource TimePickerStyle}" ValueChanged="TimePicker_ValueChanged" />
步骤2:后台代码实现
private void TimePicker_ValueChanged(object sender, RoutedPropertyChangedEventArgs<DateTime?> e) { var timePicker = sender as toolkit:TimePicker; if (timePicker == null || e.NewValue == null) return; // 查找模板内的ToggleButton并关闭Popup var toggleButton = FindVisualChild<ToggleButton>(timePicker, "_timePickerToggleButton"); toggleButton?.SetValue(ToggleButton.IsCheckedProperty, false); // 也可直接查找Popup关闭 // var popup = FindVisualChild<Popup>(timePicker, "PART_Popupx"); // popup?.SetValue(Popup.IsOpenProperty, false); } // 可视化树查找辅助方法 private T FindVisualChild<T>(DependencyObject parent, string elementName) where T : FrameworkElement { for (int i = 0; i < VisualTreeHelper.GetChildrenCount(parent); i++) { var child = VisualTreeHelper.GetChild(parent, i); if (child is T targetElement && targetElement.Name == elementName) { return targetElement; } var result = FindVisualChild<T>(child, elementName); if (result != null) return result; } return null; }
原理说明
通过FindVisualChild方法遍历TimePicker的可视化树,定位到模板内的ToggleButton或Popup,修改其状态实现关闭。注意需确保模板内元素的Name与代码中查找的名称完全一致。
注意事项
- 确保模板内ToggleButton(
_timePickerToggleButton)和Popup(PART_Popupx)的Name属性正确,否则无法找到目标元素 - XAML方式更简洁且符合WPF设计理念,优先推荐使用
- 后台代码方式尽量避免直接操作UI元素,仅在特殊场景下使用
内容的提问来源于stack exchange,提问作者Eth

