我有一个包含ItemsControl
的窗口,其中可以包含数量可变的控件。为了解决窗口高度无法容纳的情况,我将其包装在一个ScrollViewer
中,这样当项目的数量超过可用高度时,就会显示滚动条。
现在的问题是,有时在ItemsControl
中不会显示任何内容,有时会显示。因此,我将网格行的高度设置为Auto
,以允许ItemsControl
在为空时消失,或在需要时增长。但是,这意味着,即使超过窗口高度,行也会根据需要占用任意多的高度,并且不会显示垂直滚动条。
以下是演示此问题的示例窗口的一些XAML ...
<Window x:Class="DuplicateCustomerCheck.TestScrollViewerWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Test Scroll Viewer Window"
Height="450"
Width="200">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<TextBox Name="N"
TextChanged="TextBoxBase_OnTextChanged"
Grid.Row="0"
Margin="3" />
<Grid Margin="3"
Grid.Row="1">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<TextBlock Text="Possible duplicate of..."
Margin="3" />
<ScrollViewer VerticalScrollBarVisibility="Visible"
Grid.Row="1">
<ItemsControl Name="MatchingNames"
ItemsSource="{Binding MatchingNames, Mode=TwoWay}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Vertical" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Button Content="{Binding Item}" />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</ScrollViewer>
</Grid>
<TextBlock Grid.Row="2"
Margin="3"
Text="Stuff at the bottom" />
</Grid>
</Window>
出于演示的目的,这里是按钮的事件处理程序,它允许我测试不同数量的项目(注意,这是结点代码,所以没有错误检查等)……
private void TextBoxBase_OnTextChanged(object sender, TextChangedEventArgs e) {
MatchingNames.ItemsSource = Enumerable
.Range(0, int.Parse(N.Text))
.Select(n1 => new {
Item = "Button " + n1
});
}
如果我将第二个网格行的高度更改为*
,那么它可以正常工作,但这意味着ItemsControl
是永久可见的,这是我不想要的。它应该只在其中有一些项目时才会显示。
我尝试了this blog post (code here)的ScrollViewerMaxSizeBehavior
行为,但没有任何区别。
有没有人知道我怎么才能让ItemsControl
根据需要占用尽可能多的垂直空间,包括零空间,但又不会超出窗口的高度呢?
发布于 2018-12-28 05:23:57
仅使用XAML就很难解决这种情况。我会用一些计算来解决它,…
<Grid x:Name="MyGrid">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" MaxHeight="{Binding Row2MaxHeight}"/>
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<TextBox Name="N" TextChanged="TextBoxBase_OnTextChanged" Grid.Row="0" Margin="3" />
<Grid Margin="3" Grid.Row="1" x:Name="MyInnerGrid">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<TextBlock Text="Possible duplicate of..." Margin="3" />
<ScrollViewer Grid.Row="1" MaxHeight="{Binding Row2MaxHeightInner}">
<ItemsControl Name="MatchingNames" ItemsSource="{Binding MatchingNames, Mode=TwoWay}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Vertical" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Button Content="{Binding Item}" />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</ScrollViewer>
</Grid>
<TextBlock Grid.Row="2"
Margin="3"
Text="Stuff at the bottom" />
</Grid>
和代码:
public partial class MainWindow : Window, INotifyPropertyChanged {
public MainWindow() {
InitializeComponent();
DataContext = this;
SizeChanged += SizeWasChanged;
}
private void SizeWasChanged(object sender, SizeChangedEventArgs e) {
OnPropertyChanged(nameof(Row2MaxHeight));
OnPropertyChanged(nameof(Row2MaxHeightInner));
}
public double Row2MaxHeight => ActualHeight - MyGrid.RowDefinitions[0].ActualHeight - MyGrid.RowDefinitions[2].ActualHeight - 50; //50 or something is around the Size of the title bar of the window
public double Row2MaxHeightInner => Row2MaxHeight - MyInnerGrid.RowDefinitions[0].ActualHeight - 6; //6 should match the margin of the scrollviewer
private void TextBoxBase_OnTextChanged(object sender, TextChangedEventArgs e) {
MatchingNames.ItemsSource = Enumerable
.Range(0, int.Parse(N.Text))
.Select(n1 => new {
Item = "Button " + n1
});
OnPropertyChanged(nameof(Row2MaxHeight));
OnPropertyChanged(nameof(Row2MaxHeightInner));
}
public event PropertyChangedEventHandler PropertyChanged;
[NotifyPropertyChangedInvocator]
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null) {
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
发布于 2019-01-23 10:06:14
在我发布a similar question之后,我发现了这个问题。尽管您提到“因此,我将网格行的高度设置为自动,以允许ItemsControl在为空时消失,或在需要时增长”,但至少我认为我们是在问同样的问题。但是,当我尝试您的示例时,即使在空的情况下,ScrollViewer
仍然显示其滚动条,并且它正在占用空间。
无论如何,mami answered my question,虽然他们的答案对我并不完全有效,但它和Markus's answer一起让我想到了my own answer。
不过,我的答案不太适合您的情况,因为我的答案假设ItemsControl
是Grid
行中唯一的东西,而您的行中也有“...的可能副本”TextBlock
。我调整了我的答案,以考虑到TextBlock
的大小,但它并不像我希望的那样干净。作为一种可能的优化,在计算ItemsControl
的Item
的总高度时,一旦高度变得“足够大”(例如,大于Window
的高度),你可以提前退出。这样,如果你有数千个项目,但实际上只有几十个项目可以在屏幕上显示,你可以只得到几十个项目的高度,而不是数千个项目的高度。
无论如何,它可能会给您一些想法:)
XAML:
<Window x:Class="WpfApp1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<Grid ShowGridLines="True">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="*" MaxHeight="{Binding ItemMaxHeight,RelativeSource={RelativeSource Mode=FindAncestor,AncestorType=Window}}"/>
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<TextBox Name="N"
TextChanged="TextBoxBase_OnTextChanged"
Grid.Row="0"
Margin="3" />
<Grid Margin="3"
Grid.Row="1">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<TextBlock Name="tb" Text="Possible duplicate of..."
Margin="3" />
<ScrollViewer VerticalScrollBarVisibility="Visible"
Grid.Row="1">
<ItemsControl Name="MatchingNames"
ItemsSource="{Binding MatchingNames, Mode=TwoWay}"
SizeChanged="MatchingNames_SizeChanged">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Vertical" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Button Content="{Binding Item}" />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</ScrollViewer>
</Grid>
<TextBlock Grid.Row="2"
Margin="3"
Text="Stuff at the bottom" />
</Grid>
</Window>
代码隐藏:
public partial class MainWindow : Window, INotifyPropertyChanged
{
public MainWindow()
{
InitializeComponent();
}
public event PropertyChangedEventHandler PropertyChanged;
private void TextBoxBase_OnTextChanged(object sender, TextChangedEventArgs e)
{
MatchingNames.ItemsSource = Enumerable
.Range(0, int.Parse(N.Text))
.Select(n1 => new
{
Item = "Button " + n1
});
}
public double ItemMaxHeight
{
get
{
if (MatchingNames == null)
return double.PositiveInfinity;
double height = 0.0;
var icg = MatchingNames.ItemContainerGenerator;
for (int i = 0; i < MatchingNames.Items.Count; i++)
height += (icg.ContainerFromIndex(i) as FrameworkElement).ActualHeight;
return height
+ tb.Margin.Top + tb.ActualHeight + tb.Margin.Bottom
+ 6.0; // 6 should match the margin of the scrollviewer
}
}
private void MatchingNames_SizeChanged(object sender, SizeChangedEventArgs e)
{
if (e.HeightChanged)
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("ItemMaxHeight"));
}
}
https://stackoverflow.com/questions/53949585
复制相似问题