gpt4 book ai didi

c# - 在 WPF 中折叠网格行

转载 作者:太空狗 更新时间:2023-10-29 18:30:41 25 4
gpt4 key购买 nike

我创建了一个从 RowDefinition 扩展而来的自定义 WPF 元素当 Collapsed 时应该折叠网格中的行元素的属性设置为 True .

它通过在样式中使用转换器和数据触发器将行高设置为 0 来实现。它基于此 SO Answer .

在下面的示例中,当网格拆分器位于窗口的一半以上时,此方法非常有效。但是,当它不到一半时,行仍然折叠,但第一行不会展开。相反,原来行所在的位置只有一个白色间隙。这可以在下图中看到。

Picture shows under half, the bottom row doesn't disappear, but over half it does

类似地,如果MinHeightMaxHeight在折叠的任何行上设置,它根本不再折叠该行。我试图通过在数据触发器中为这些属性添加 setter 来解决这个问题,但没有解决。

我的问题是可以采取哪些不同的方式来使行的大小或 MinHeight 无关紧要/MaxHeight已设置,它只是能够折叠行?


MCVE

MainWindow.xaml.cs

using System;
using System.ComponentModel;
using System.Globalization;
using System.Runtime.CompilerServices;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;

namespace RowCollapsibleMCVE
{
public partial class MainWindow : INotifyPropertyChanged
{
public MainWindow()
{
InitializeComponent();
DataContext = this;
}

public event PropertyChangedEventHandler PropertyChanged;

private void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}

private bool isCollapsed;

public bool IsCollapsed
{
get => isCollapsed;
set
{
isCollapsed = value;
OnPropertyChanged();
}
}
}

public class CollapsibleRow : RowDefinition
{
#region Default Values
private const bool COLLAPSED_DEFAULT = false;
private const bool INVERT_COLLAPSED_DEFAULT = false;
#endregion

#region Dependency Properties
public static readonly DependencyProperty CollapsedProperty =
DependencyProperty.Register("Collapsed", typeof(bool), typeof(CollapsibleRow), new PropertyMetadata(COLLAPSED_DEFAULT));

public static readonly DependencyProperty InvertCollapsedProperty =
DependencyProperty.Register("InvertCollapsed", typeof(bool), typeof(CollapsibleRow), new PropertyMetadata(INVERT_COLLAPSED_DEFAULT));
#endregion

#region Properties
public bool Collapsed {
get => (bool)GetValue(CollapsedProperty);
set => SetValue(CollapsedProperty, value);
}

public bool InvertCollapsed {
get => (bool)GetValue(InvertCollapsedProperty);
set => SetValue(InvertCollapsedProperty, value);
}
#endregion
}

public class BoolVisibilityConverter : IMultiValueConverter
{
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
{
if (values.Length > 0 && values[0] is bool collapsed)
{
if (values.Length > 1 && values[1] is bool invert && invert)
{
collapsed = !collapsed;
}

return collapsed ? Visibility.Collapsed : Visibility.Visible;
}

return Visibility.Collapsed;
}

public object[] ConvertBack(object value, Type[] targetType, object parameter, CultureInfo culture)
{
throw new NotSupportedException();
}
}
}

MainWindow.xaml

<Window x:Class="RowCollapsibleMCVE.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"
xmlns:local="clr-namespace:RowCollapsibleMCVE"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<Window.Resources>
<Visibility x:Key="CollapsedVisibilityVal">Collapsed</Visibility>
<local:BoolVisibilityConverter x:Key="BoolVisibilityConverter"/>

<Style TargetType="{x:Type local:CollapsibleRow}">
<Style.Triggers>
<DataTrigger Value="{StaticResource CollapsedVisibilityVal}">
<DataTrigger.Binding>
<MultiBinding Converter="{StaticResource BoolVisibilityConverter}">
<Binding Path="Collapsed"
RelativeSource="{RelativeSource Self}"/>
<Binding Path="InvertCollapsed"
RelativeSource="{RelativeSource Self}"/>
</MultiBinding>
</DataTrigger.Binding>
<DataTrigger.Setters>
<Setter Property="MinHeight" Value="0"/>
<Setter Property="Height" Value="0"/>
<Setter Property="MaxHeight" Value="0"/>
</DataTrigger.Setters>
</DataTrigger>
</Style.Triggers>
</Style>
</Window.Resources>

<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<CheckBox Content="Collapse Row"
IsChecked="{Binding IsCollapsed}"/>
<Grid Row="1">
<Grid.RowDefinitions>
<local:CollapsibleRow Height="3*" />
<local:CollapsibleRow Height="Auto" />
<local:CollapsibleRow Collapsed="{Binding IsCollapsed}" Height="*" /> <!-- Using [MaxHeight="300"] breaks this completely -->
</Grid.RowDefinitions>
<StackPanel Background="Red"/>

<GridSplitter Grid.Row="1"
Height="10"
HorizontalAlignment="Stretch">
<GridSplitter.Visibility>
<MultiBinding Converter="{StaticResource BoolVisibilityConverter}" >
<Binding Path="IsCollapsed"/>
</MultiBinding>
</GridSplitter.Visibility>
</GridSplitter>

<StackPanel Background="Blue"
Grid.Row="2">
<StackPanel.Visibility>
<MultiBinding Converter="{StaticResource BoolVisibilityConverter}" >
<Binding Path="IsCollapsed"/>
</MultiBinding>
</StackPanel.Visibility>
</StackPanel>
</Grid>
</Grid>
</Window>

最佳答案

您只需要缓存可见行的高度即可。之后,您不再需要转换器或切换包含控件的可见性。

可折叠行

public class CollapsibleRow : RowDefinition
{
#region Fields
private GridLength cachedHeight;
private double cachedMinHeight;
#endregion

#region Dependency Properties
public static readonly DependencyProperty CollapsedProperty =
DependencyProperty.Register("Collapsed", typeof(bool), typeof(CollapsibleRow), new PropertyMetadata(false, OnCollapsedChanged));

private static void OnCollapsedChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
if(d is CollapsibleRow row && e.NewValue is bool collapsed)
{
if(collapsed)
{
if(row.MinHeight != 0)
{
row.cachedMinHeight = row.MinHeight;
row.MinHeight = 0;
}
row.cachedHeight = row.Height;
}
else if(row.cachedMinHeight != 0)
{
row.MinHeight = row.cachedMinHeight;
}
row.Height = collapsed ? new GridLength(0) : row.cachedHeight;
}
}
#endregion

#region Properties
public bool Collapsed
{
get => (bool)GetValue(CollapsedProperty);
set => SetValue(CollapsedProperty, value);
}
#endregion
}

XAML

<Window x:Class="RowCollapsibleMCVE.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"
xmlns:local="clr-namespace:RowCollapsibleMCVE"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<CheckBox Content="Collapse Row"
IsChecked="{Binding IsCollapsed}"/>
<Grid Row="1">
<Grid.RowDefinitions>
<local:CollapsibleRow Height="3*" MinHeight="0.0001"/>
<local:CollapsibleRow Collapsed="{Binding IsCollapsed}" Height="Auto" />
<local:CollapsibleRow Collapsed="{Binding IsCollapsed}" Height="*" /> <!-- Using [MinHeight="50" MaxHeight="100"] behaves as expected -->
</Grid.RowDefinitions>
<StackPanel Background="Red"/>
<GridSplitter Grid.Row="1" Height="10" HorizontalAlignment="Stretch" />
<StackPanel Background="Blue" Grid.Row="2" />
</Grid>
</Grid>
</Window>

您应该在可折叠行(我们示例中的第三行)上有一个 MaxHeight,或者在与分离器。这是为了确保当您将拆分器一直向上并切换可见性时,星形大小的行具有大小。只有这样,它才能接管剩余的空间。


更新

正如@Ivan 在他的帖子中提到的那样,折叠行​​中包含的控件仍然是可聚焦的,允许用户在不应该访问它们时访问它们。诚然,手动设置所有控件的可见性可能很痛苦,尤其是对于大型 XAML。因此,让我们添加一些自定义行为以将折叠的行与其控件同步。

  1. 问题

首先,使用上面的代码运行示例,然后通过选中复选框折叠底部的行。现在,按一次 TAB 键并使用向上箭头键移动 GridSplitter。如您所见,即使拆分器不可见,用户仍然可以访问它。

  1. 修复

添加一个新文件 Extensions.cs 来承载行为。

using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using RowCollapsibleMCVE;

namespace Extensions
{
[ValueConversion(typeof(bool), typeof(bool))]
public class BooleanConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
return !(bool)value;
}

public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
return Binding.DoNothing;
}
}

public class GridHelper : DependencyObject
{
#region Attached Property

public static readonly DependencyProperty SyncCollapsibleRowsProperty =
DependencyProperty.RegisterAttached(
"SyncCollapsibleRows",
typeof(Boolean),
typeof(GridHelper),
new FrameworkPropertyMetadata(false,
FrameworkPropertyMetadataOptions.AffectsRender,
new PropertyChangedCallback(OnSyncWithCollapsibleRows)
));

public static void SetSyncCollapsibleRows(UIElement element, Boolean value)
{
element.SetValue(SyncCollapsibleRowsProperty, value);
}

private static void OnSyncWithCollapsibleRows(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
if (d is Grid grid)
{
grid.Loaded += (o,ev) => SetBindingForControlsInCollapsibleRows((Grid)o);
}
}

#endregion

#region Logic

private static IEnumerable<UIElement> GetChildrenFromPanels(IEnumerable<UIElement> elements)
{
Queue<UIElement> queue = new Queue<UIElement>(elements);
while (queue.Any())
{
var uiElement = queue.Dequeue();
if (uiElement is Panel panel)
{
foreach (UIElement child in panel.Children) queue.Enqueue(child);
}
else
{
yield return uiElement;
}
}
}

private static IEnumerable<UIElement> ElementsInRow(Grid grid, int iRow)
{
var rowRootElements = grid.Children.OfType<UIElement>().Where(c => Grid.GetRow(c) == iRow);

if (rowRootElements.Any(e => e is Panel))
{
return GetChildrenFromPanels(rowRootElements);
}
else
{
return rowRootElements;
}
}

private static BooleanConverter MyBooleanConverter = new BooleanConverter();

private static void SyncUIElementWithRow(UIElement uiElement, CollapsibleRow row)
{
BindingOperations.SetBinding(uiElement, UIElement.FocusableProperty, new Binding
{
Path = new PropertyPath(CollapsibleRow.CollapsedProperty),
Source = row,
Converter = MyBooleanConverter
});
}

private static void SetBindingForControlsInCollapsibleRows(Grid grid)
{
for (int i = 0; i < grid.RowDefinitions.Count; i++)
{
if (grid.RowDefinitions[i] is CollapsibleRow row)
{
ElementsInRow(grid, i).ToList().ForEach(uiElement => SyncUIElementWithRow(uiElement, row));
}
}
}

#endregion
}
}
  1. 更多测试

更改 XAML 以添加行为和一些文本框(它们也是可聚焦的)。

<Window x:Class="RowCollapsibleMCVE.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"
xmlns:local="clr-namespace:RowCollapsibleMCVE"
xmlns:ext="clr-namespace:Extensions"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<CheckBox Content="Collapse Row" IsChecked="{Binding IsCollapsed}"/>
<!-- Set the desired behavior through an Attached Property -->
<Grid ext:GridHelper.SyncCollapsibleRows="True" Row="1">
<Grid.RowDefinitions>
<RowDefinition Height="3*" MinHeight="0.0001" />
<local:CollapsibleRow Collapsed="{Binding IsCollapsed}" Height="Auto" />
<local:CollapsibleRow Collapsed="{Binding IsCollapsed}" Height="*" />
</Grid.RowDefinitions>
<StackPanel Background="Red">
<TextBox Width="100" Margin="40" />
</StackPanel>
<GridSplitter Grid.Row="1" Height="10" HorizontalAlignment="Stretch" />
<StackPanel Grid.Row="2" Background="Blue">
<TextBox Width="100" Margin="40" />
</StackPanel>
</Grid>
</Grid>
</Window>

最后:

  • 逻辑对 XAML 完全隐藏(干净)。
  • 我们仍在提供灵 active :

    • 对于每个 CollapsibleRow,您可以将 Collapsed 绑定(bind)到不同的变量。

    • 不需要该行为的行可以使用基本 RowDefinition(按需应用)。


更新 2

正如@Ash 在评论中指出的那样,您可以使用 WPF's native caching存储高度值。产生具有自治属性的非常干净的代码,每个处理自己的 => 健壮的代码。例如,使用下面的代码,您将无法在折叠行时移动 GridSplitter,即使没有应用该行为也是如此。

当然,控件仍然可以访问,允许用户触发事件。所以我们仍然需要这种行为,但是 CoerceValueCallback 确实在 Collapsed 和我们的 CollapsibleRow 的各种高度依赖属性之间提供了一致的链接.

public class CollapsibleRow : RowDefinition
{
public static readonly DependencyProperty CollapsedProperty;

public bool Collapsed
{
get => (bool)GetValue(CollapsedProperty);
set => SetValue(CollapsedProperty, value);
}

static CollapsibleRow()
{
CollapsedProperty = DependencyProperty.Register("Collapsed",
typeof(bool), typeof(CollapsibleRow), new PropertyMetadata(false, OnCollapsedChanged));

RowDefinition.HeightProperty.OverrideMetadata(typeof(CollapsibleRow),
new FrameworkPropertyMetadata(new GridLength(1, GridUnitType.Star), null, CoerceHeight));

RowDefinition.MinHeightProperty.OverrideMetadata(typeof(CollapsibleRow),
new FrameworkPropertyMetadata(0.0, null, CoerceHeight));

RowDefinition.MaxHeightProperty.OverrideMetadata(typeof(CollapsibleRow),
new FrameworkPropertyMetadata(double.PositiveInfinity, null, CoerceHeight));
}

private static object CoerceHeight(DependencyObject d, object baseValue)
{
return (((CollapsibleRow)d).Collapsed) ? (baseValue is GridLength ? new GridLength(0) : 0.0 as object) : baseValue;
}

private static void OnCollapsedChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
d.CoerceValue(RowDefinition.HeightProperty);
d.CoerceValue(RowDefinition.MinHeightProperty);
d.CoerceValue(RowDefinition.MaxHeightProperty);
}
}

关于c# - 在 WPF 中折叠网格行,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/54020066/

25 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com