Warning: file_get_contents(/data/phpspider/zhask/data//catemap/4/wpf/14.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
C# WPF中的折叠网格行_C#_Wpf_Xaml_Datatrigger_Wpf Grid - Fatal编程技术网

C# WPF中的折叠网格行

C# WPF中的折叠网格行,c#,wpf,xaml,datatrigger,wpf-grid,C#,Wpf,Xaml,Datatrigger,Wpf Grid,我已经创建了一个从RowDefinition扩展而来的自定义WPF元素,当元素的Collapsed属性设置为True时,该元素应该在网格中折叠行 它通过在样式中使用转换器和datatrigger将行的高度设置为0来实现。基于此 在下面的示例中,当栅格拆分器位于窗口上方的一半以上时,此功能非常有效。但是,当距离不足一半时,行仍会折叠,但第一行不会展开。取而代之的是,在过去的行之间只有一个白色的间隙。这可以在下图中看到 类似地,如果在任何折叠的行上设置了MinHeight或MaxHeight,则它

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

它通过在样式中使用转换器和datatrigger将行的高度设置为0来实现。基于此

在下面的示例中,当栅格拆分器位于窗口上方的一半以上时,此功能非常有效。但是,当距离不足一半时,行仍会折叠,但第一行不会展开。取而代之的是,在过去的行之间只有一个白色的间隙。这可以在下图中看到

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

我的问题是,有什么不同的方法可以使行的大小无关紧要,或者如果设置了
MinHeight
/
MaxHeight
,就可以折叠行


MCVE

MainWindow.xaml.cs

MainWindow.xaml


崩溃

上述示例在技术上是错误的

它实际上是试图强制行的高度为0,这不是您想要或应该做的-问题是,即使高度为0,tab键也会穿过控件,并且“讲述人”将读取这些控件。从本质上讲,这些控件仍然存在,并且完全可单击、功能齐全且可访问,只是它们没有显示在窗口上,但它们仍然可以以各种方式访问,并且可能会影响应用程序的工作

第二(导致您描述的问题的原因是您没有描述上面的问题,尽管它们也很重要,不应该被忽略),您有
GridSplitter
,并且如上所述,即使您强制其高度为0(如上所述),它仍然可以正常工作
GridSplitter
意味着在一天结束时,您不受布局的控制,而受用户的控制

相反,您应该使用普通的
行定义
,将其高度设置为
自动
,然后将行内容的
可见性
设置为
折叠
——当然,您可以使用数据绑定和转换器


编辑:进一步澄清-在上面的代码中,您设置了名为
Collapsed
InvertCollapsed
的新属性。因为它们的命名方式对折叠的行没有任何影响,所以它们也可以称为Property1和Property2。它们以一种非常奇怪的方式在
DataTrigger
中使用-当它们的值更改时,该值将转换为
Visibility
,然后如果转换后的值是
折叠的
,则调用强制行高为0的设置器。所以有人玩了很多的布景,让它看起来像是在坍塌什么东西,但他没有,他只是改变了高度,这是完全不同的事情。这就是问题的根源。我当然建议完全避免这种方法,但如果您发现它对您的应用程序有好处,那么您需要做的最简单的事情就是在设置GridSplitter的第二行中避免这种方法,因为如果您不这样做,那么您的请求就变得不可能了。

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

可折叠行

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>
  • 更多测试
  • 更改XAML以添加行为和一些文本框(它们也是可聚焦的)


    Hi Ivan,通常当我使用这个时,组件可见性被设置为使用相同的绑定bool和convertor折叠,我将更新问题以显示这一点。至于自动建议,这并不理想,因为在我当前的程序中,我正在寻找一种方式,当屏幕初始化时,底部部分占据大约四分之一的可用空间,然后用户可以操纵/折叠它的大小。因此,我为什么要使用问题中的MCVE嗨,我试图解释这根本不像在其他情况下那样是折叠的,您可以检查是否添加控件并按照建议使用tab键。关键的部分是将GridSplitter可见性设置为Collapsed,而不是它的行,这将使它适用于您,即使您在应用程序的其余部分使用相同的代码。然而,我所描述的由您的方法引起的问题将仍然存在,但如果您对这些问题没有意见,那么您可以使用它们。@Dan我更新了进一步的澄清,因为我发现您不清楚,然后我猜其他人也不清楚,答案是正确的。@Ivan Well spotted+1。然而,对于大型XAML来说,手动设置所有控件的可见性可能会非常痛苦。我添加了行为来处理控件的绘图焦点。@Funk这就是数据绑定的目的,这样您就可以用一行o来更改所有内容
    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
    }
    
    <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>
    
    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
        }
    }
    
    <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>
    
    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);
        }
    }