如何处理项目控制大小更改正确以显示/折叠滚动按钮



我构建了一个用户控件,该控件应在应用程序底部显示状态指示器列表。我使用ItemsControl来创建列表,并使其能够通过鼠标滚轮水平滚动,但也添加了两个按钮(左和右)来替换通常的滚动条(因为滚动条会与外观冲突,在这种情况下会很大)。

如果列表中没有足够的项或控件足够宽以显示所有项,我想折叠按钮,当然在需要滚动时立即再次显示它们。

目前,我使用ScrollViewerSizeChanged事件来检测宽度是否已更改,以及ItemsControl的任何项目是否不可见,因此需要滚动。(请参阅文章底部的 C# 代码。

我的问题是,只要我通过用鼠标捏住窗口边缘并以这种方式调整大小来更改大小,它就可以正常工作,但是一旦以编程方式更改窗口大小或通过双击窗口标题使其全屏(或使用最大化/最小化按钮),它就不起作用。

这是我的用户控件:

<Grid x:Name="grid">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<dx:SimpleButton x:Name="scroll_left_button" Grid.Column="0" Content="←"/>
<Border Background="#FF00ACDC" Grid.Column="1" BorderThickness="0">
<ItemsControl x:Name="items_control" w3ClientUi:ScrollViewerHelper.UseHorizontalScrolling="True" 
ScrollViewer.HorizontalScrollBarVisibility="Hidden" ScrollViewer.VerticalScrollBarVisibility="Disabled" 
ScrollViewer.CanContentScroll="True" >
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Horizontal"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.Template>
<ControlTemplate TargetType="ItemsControl">
<ScrollViewer HorizontalScrollBarVisibility="Hidden">
<ItemsPresenter/>
</ScrollViewer>
</ControlTemplate>
</ItemsControl.Template>
<ItemsControl.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<!-- dummy template -->
<StackPanel.ToolTip>
<TextBlock Text="{Binding Name}"/>
</StackPanel.ToolTip>
<panda:PndLabel Padding="0 10 2 10" Content="{Binding Number}" FontSize="16" FontWeight="Normal"
Foreground="White" Margin="0 0 2 0"/>
</StackPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
<!-- ItemsSource is set in Code -->
</ItemsControl>
</Border>
<dx:SimpleButton Grid.Column="2" x:Name="scroll_right_button" Content="→"/>
</Grid>

在 Code Behind 中,我在加载ItemsControl后立即从ScrollViewer实例获取该实例,然后附加到SizeChanged事件。 当通过窗口标题或以编程方式更改窗口大小时,会引发事件,但ScrollableWidth尚未更新,因此我的按钮仍然全屏可见(或者在变小时仍折叠)。

scroll_viewer.SizeChanged += (s, e) =>
{
if (!e.WidthChanged) return;
if (scroll_viewer?.ScrollableWidth > 0)
{
scroll_left_button.Visibility = Visibility.Visible;
scroll_right_button.Visibility = Visibility.Visible;
}
else
{
scroll_left_button.Visibility = Visibility.Collapsed;
scroll_right_button.Visibility = Visibility.Collapsed;
}
};

因此,在周末带着新鲜的头脑回来后,我认为,就我而言,处理SizeChanged事件是错误的方法,因为我实际上需要知道的是我的ItemsControl是否有任何项目需要滚动以及数字是否已更改。

在我的问题中,我已经检查了ScrollViewerScrollableWidth属性。它显示了有多少项目不可见且需要滚动,因此检查它是否已更改以及新值是否大于零就足够了。

ScrollableWidth是一个DependencyProperty,所以我考虑绑定它并倾听更改的事件。

所以我首先要做的是在我UserControl上创建新DependencyProperty

// Dependency Property is Private since I only need it internally
private static readonly DependencyProperty scrollable_width_property =
DependencyProperty.Register(nameof(scrollable_width), typeof(double),
typeof(MyUserControl),
new FrameworkPropertyMetadata(null) { BindsTwoWayByDefault = false, PropertyChangedCallback = property_changed_callback });
// Wrapper only has get because ScrollableWidth is read only anyway
private double scrollable_width => (double)GetValue(scrollable_width_property);
// Listening to the change
private static void property_changed_callback(DependencyObject dpo, DependencyPropertyChangedEventArgs args)
{
var o = dpo as MyUserControl;
o?.SetButtonVisibility((double)args.NewValue > 0);
}

我删除了scroll_viewer.SizeChanged事件,而是在用户控件上创建了一个新的公共方法来更改按钮可见性。

public void SetButtonVisibility(bool visible)
{
if (scroll_viewer == null) return;
if (visible)
{
scroll_left_button.Visibility = Visibility.Visible;
scroll_right_button.Visibility = Visibility.Visible;
}
else
{
scroll_left_button.Visibility = Visibility.Collapsed;
scroll_right_button.Visibility = Visibility.Collapsed;
}
}

最后要做的是实际绑定。 我做一次,在获得实际ScrollViewer后的ItemsControlLoaded事件中。

var binding = new Binding(nameof(ScrollViewer.ScrollableWidth))
{
Source = scroll_viewer
};
SetBinding(scrollable_width_property, binding);

现在,每当ItemControl需要滚动(或不需要)时,无论大小如何变化,按钮的可见性都会改变。现在,当使用窗口标题中的最大化/最小化按钮时,它也可以工作。 它可能不是最好的实现,因为它可能更好地在 xaml 中使用样式触发器而不是SetButtonVisibility方法,但它可以完成工作。

编辑:就我而言,我还必须在ItemsContorolLoadedEvent中添加一个SetButtonVisibility(scroll_viewer.ScrollableWidth > 0);,因为回调不会在启动时触发。

最新更新