WPF GridSplitter - 保存和恢复位置并按比例分割

发布于 2024-10-18 02:50:12 字数 3311 浏览 1 评论 0原文

我正在创建一个 3 列 UI,列之间有网格分隔符。我需要保存列的状态,以便如果用户关闭并重新打开应用程序,它看起来就像他们离开它一样。但我也试图让列按比例分割 - 我的意思是,如果你拉伸窗口,所有三个面板都会增长,如果你移动左分割器,它会改变左列和中列之间的划分。

我目前仅实现了第一个要求 - 它保存了列宽的状态。我还使这些列强制所有三列的最小宽度。但据我了解,告诉拆分器按比例拆分的方法是使用星形大小的列宽度。由于我已经使用 Width 属性来保存和恢复状态,因此我不确定是否可以完成我想要的任务。

有没有人设法既保存列宽的状态又使分割成比例?

以下是我当前获得的一些代码:

   <Grid x:Name="mainGrid" HorizontalAlignment="Stretch" VerticalAlignment="Stretch">
        <Grid.ColumnDefinitions>
            <ColumnDefinition x:Name="leftColumn" Width="{Binding MainWindowLeftColumnWidth, Mode=TwoWay, Source={x:Static prop:Settings.Default}}" MinWidth="200" MaxWidth="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:MainWindow}}, Path=LeftColumnMaxWidth, Mode=OneWay}"/>
            <ColumnDefinition Width="Auto"/>
            <ColumnDefinition x:Name="centerColumn" Width="{Binding MainWindowCenterColumnWidth, Mode=TwoWay, Source={x:Static prop:Settings.Default}}" MinWidth="300" MaxWidth="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:MainWindow}}, Path=CenterColumnMaxWidth, Mode=OneWay}"/>
            <ColumnDefinition Width="Auto"/>
            <ColumnDefinition x:Name="rightColumn" Width="*" MinWidth="500"/>
        </Grid.ColumnDefinitions>
        <StackPanel x:Name="leftPanel" Grid.Column="0" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Margin="0"/>
        <GridSplitter x:Name="leftSplitter" Grid.Column="1" HorizontalAlignment="Center" VerticalAlignment="Stretch" ResizeBehavior="PreviousAndNext" Width="5" ResizeDirection="Columns"/>
        <StackPanel x:Name="centerPanel" Grid.Column="2" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Margin="0"/>
        <GridSplitter x:Name="rightSplitter" Grid.Column="3" HorizontalAlignment="Center" VerticalAlignment="Stretch" ResizeBehavior="PreviousAndNext" Width="5" ResizeDirection="Columns"/>
        <StackPanel x:Name="rightPanel" Grid.Column="4" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Margin="0" SizeChanged="rightPanel_SizeChanged"/>
    </Grid>

我对 LeftColumnMaxWidth 和 CenterColumnMaxWidth 都有 double 类型的依赖属性。 rightPanel_SizeChanged 处理程序以及窗口 Loaded 处理程序都调用此方法:

    private void CalculateMaxWidths()
    {
          FrameworkElement content = Content as FrameworkElement;
          if (content != null)
          {
              LeftColumnMaxWidth = content.ActualWidth
                                 - leftSplitter.ActualWidth
                                 - centerColumn.ActualWidth
                                 - rightSplitter.ActualWidth
                                 - rightColumn.MinWidth;
              CenterColumnMaxWidth = content.ActualWidth
                                   - leftColumn.ActualWidth
                                   - leftSplitter.ActualWidth
                                   - rightSplitter.ActualWidth
                                   - rightColumn.MinWidth;
          }
    }

我仍然需要做一些工作来确保调整窗口大小不会剪切右列。我认为该解决方案可能与尝试使分离器按比例分离有关。我当前设置的特别奇怪的行为是左分割器调整左右列的大小,并保持中心列的大小固定。

我不害怕处理 SizeChanged 或 DragDelta 来实现我的目标。但我相信我实际上不能做的是设置前两列的 Width 属性,因为这会破坏我与保存状态的用户设置的绑定。

预先感谢您的帮助。

I am creating a 3-column UI with grid splitters between the columns. I have the requirement to save the sate of the columns so that if the user closes and reopens the app it looks just like they left it. But I am also trying to get the columns to split proportionately - by which I mean if you stretch the window, all three panels grow and if you move the left splitter it changes the division between the left and center columns.

What I have currently achieves only the first requirement - it saves the state of the column widths. I have also made the columns enforce minimum widths for all three columns. But as I understand it, the way to tell a splitter to split proportionately is to use star-sized column widths. Since I am using the Width property already to save and restore the state, I'm not sure I can accomplish what I want to.

Has anyone managed to both save the state of column widths AND have the split be proportional?

Here is some code for what I've got currently:

   <Grid x:Name="mainGrid" HorizontalAlignment="Stretch" VerticalAlignment="Stretch">
        <Grid.ColumnDefinitions>
            <ColumnDefinition x:Name="leftColumn" Width="{Binding MainWindowLeftColumnWidth, Mode=TwoWay, Source={x:Static prop:Settings.Default}}" MinWidth="200" MaxWidth="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:MainWindow}}, Path=LeftColumnMaxWidth, Mode=OneWay}"/>
            <ColumnDefinition Width="Auto"/>
            <ColumnDefinition x:Name="centerColumn" Width="{Binding MainWindowCenterColumnWidth, Mode=TwoWay, Source={x:Static prop:Settings.Default}}" MinWidth="300" MaxWidth="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:MainWindow}}, Path=CenterColumnMaxWidth, Mode=OneWay}"/>
            <ColumnDefinition Width="Auto"/>
            <ColumnDefinition x:Name="rightColumn" Width="*" MinWidth="500"/>
        </Grid.ColumnDefinitions>
        <StackPanel x:Name="leftPanel" Grid.Column="0" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Margin="0"/>
        <GridSplitter x:Name="leftSplitter" Grid.Column="1" HorizontalAlignment="Center" VerticalAlignment="Stretch" ResizeBehavior="PreviousAndNext" Width="5" ResizeDirection="Columns"/>
        <StackPanel x:Name="centerPanel" Grid.Column="2" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Margin="0"/>
        <GridSplitter x:Name="rightSplitter" Grid.Column="3" HorizontalAlignment="Center" VerticalAlignment="Stretch" ResizeBehavior="PreviousAndNext" Width="5" ResizeDirection="Columns"/>
        <StackPanel x:Name="rightPanel" Grid.Column="4" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Margin="0" SizeChanged="rightPanel_SizeChanged"/>
    </Grid>

I have dependency properties of type double for both LeftColumnMaxWidth and CenterColumnMaxWidth. And the rightPanel_SizeChanged handler as well as the window Loaded handler both call this method:

    private void CalculateMaxWidths()
    {
          FrameworkElement content = Content as FrameworkElement;
          if (content != null)
          {
              LeftColumnMaxWidth = content.ActualWidth
                                 - leftSplitter.ActualWidth
                                 - centerColumn.ActualWidth
                                 - rightSplitter.ActualWidth
                                 - rightColumn.MinWidth;
              CenterColumnMaxWidth = content.ActualWidth
                                   - leftColumn.ActualWidth
                                   - leftSplitter.ActualWidth
                                   - rightSplitter.ActualWidth
                                   - rightColumn.MinWidth;
          }
    }

I still have some work to do to make sure that resizing the window doesn't clip the right column. I think that solution may be related to trying to make the splitters split proportionately. The particularly peculiar behavior of my current set up is that the left splitter resizes the left and right columns, and leaves the center column size fixed.

I am not afraid of handling SizeChanged or DragDelta to achieve my goals. But what I believe I cannot do is actually set the Width property of the first two columns, as that would destroy my binding to the user setting that saves the state.

Thank you in advance for any help.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

心碎无痕… 2024-10-25 02:50:12

所以我相信我已经弄清楚了。我的settings.settings 中的一些旧值可能导致了我的问题,并且我输入的默认值也可能导致了我的问题。但这就是我所做的:

  1. 更改我的用户设置以保存所有三个(不仅仅是左边两个)列宽度。并将它们保存为字符串。
  2. 将用户设置中的默认值(以及列的宽度属性)设置为 200* 之类的值。
  3. 在所有三列上仅设置 MinWidth - 而不是最大值。
  4. 使用 GridLengthConverter 手动加载和保存列的用户设置。

我不是 100% 相信这是最好的方法,但它似乎确实有效,这让我很高兴。如果其他人遇到麻烦并看到这篇文章,这里是工作 XAML:

    <Grid x:Name="mainGrid" HorizontalAlignment="Stretch" VerticalAlignment="Stretch">
        <Grid.ColumnDefinitions>
            <ColumnDefinition x:Name="leftColumn" MinWidth="200" Width="200*"/>
            <ColumnDefinition Width="Auto"/>
            <ColumnDefinition x:Name="centerColumn" MinWidth="300" Width="300*"/>
            <ColumnDefinition Width="Auto"/>
            <ColumnDefinition x:Name="rightColumn" MinWidth="498" Width="498*"/>
        </Grid.ColumnDefinitions>
        <StackPanel x:Name="leftPanel" Grid.Column="0" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Margin="0"/>
        <GridSplitter x:Name="leftSplitter" Grid.Column="1" HorizontalAlignment="Center" VerticalAlignment="Stretch" ResizeBehavior="PreviousAndNext" Width="5" ResizeDirection="Columns"/>
        <StackPanel x:Name="centerPanel" Grid.Column="2" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Margin="0"/>
        <GridSplitter x:Name="rightSplitter" Grid.Column="3" HorizontalAlignment="Center" VerticalAlignment="Stretch" ResizeBehavior="PreviousAndNext" Width="5" ResizeDirection="Columns"/>
        <StackPanel x:Name="rightPanel" Grid.Column="4" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Margin="0" SizeChanged="rightPanel_SizeChanged"/>
    </Grid>

该大小更改事件仍然存在,仅用于调试跟踪。我输出 Width 的值来看看发生了什么。奇怪的是,在展开所有内容并返回到窗口的最小尺寸后,右列宽度仍然较大。但由于它们都有最小宽度,并且宽度都是星形大小,所以它本身就可以解决。

我确实尝试将其放回绑定中,但由于我现在存储一个字符串,并且 GridLengthConverter 是 TypeConverter,而不是 IValueConverter,因此它不起作用。我认为可以将这些值存储为 GridLengths,尽管我已经达到了对自己所做的事情感到满意的程度。所以我的加载和保存是这样的:

    protected override void OnSourceInitialized(EventArgs e)
    {
        base.OnSourceInitialized(e);

        //...

        try
        {
            GridLengthConverter converter = new GridLengthConverter();
            leftColumn.Width = (GridLength)converter.ConvertFromString(Settings.Default.MainWindowLeftColumnWidth);
            centerColumn.Width = (GridLength)converter.ConvertFromString(Settings.Default.MainWindowCenterColumnWidth);
            rightColumn.Width = (GridLength)converter.ConvertFromString(Settings.Default.MainWindowRightColumnWidth);

            Trace.WriteLine(string.Format("LOADED Left: {0}, Center: {1}, Right {2}", leftColumn.Width, centerColumn.Width, rightColumn.Width));
        }
        catch (Exception)
        {
            // Fail silently, the worse case is we go with the defaults, it's going to be okay
        }
    }

    protected override void OnClosing(System.ComponentModel.CancelEventArgs e)
    {
        base.OnClosing(e);

        //...

        try
        {
            GridLengthConverter converter = new GridLengthConverter();
            Settings.Default.MainWindowLeftColumnWidth = converter.ConvertToString(leftColumn.Width);
            Settings.Default.MainWindowCenterColumnWidth = converter.ConvertToString(centerColumn.Width);
            Settings.Default.MainWindowRightColumnWidth = converter.ConvertToString(rightColumn.Width);

            Trace.WriteLine(string.Format("SAVED Left: {0}, Center: {1}, Right {2}", Settings.Default.MainWindowLeftColumnWidth, Settings.Default.MainWindowCenterColumnWidth, Settings.Default.MainWindowRightColumnWidth));
        }
        catch (Exception)
        {
            // Fail silently, the worst case is we don't save a little something, it's going to be okay
        }
    }

这一切都对我有用。所以我就去吧!

编辑:我后来做了一些改进,以防止右列保持更大的“好奇心”。我现在将所有面板尺寸更改转到一个事件处理程序:

    private void PanelSizeChanged(object sender, SizeChangedEventArgs e)
    {
        // In order to keep the resizing from losing proportionality, we'll force it to be proportional to the current size.
        // Otherwise the right panel edges up and up while the other two remain at their starting 200*/300* values.
        // And when that happens eventually resizing the window only resizes the right panel, not proportionately as it does at the start.
        leftColumn.Width = new GridLength(leftColumn.ActualWidth, GridUnitType.Star);
        centerColumn.Width = new GridLength(centerColumn.ActualWidth, GridUnitType.Star);
        rightColumn.Width = new GridLength(rightColumn.ActualWidth, GridUnitType.Star);
    }

So I believe I have figured this out. It is possible that some old values in my settings.settings were causing me issues, and it's possible that the default values I put in caused me issues. But here's what I did:

  1. Changed my user settings to save all THREE (not just the left two) column widths. And save them as strings.
  2. Set the default in the user settings (as well as the width property on the columns) to something like 200*.
  3. Set only the MinWidth - not the max - on all three columns.
  4. Manually load and save the user settings for the columns using a GridLengthConverter.

I'm not 100% convinced this is the best way, but it does seem to work, which makes me quite happy. In case anyone else has trouble and comes across this post, here is the working XAML:

    <Grid x:Name="mainGrid" HorizontalAlignment="Stretch" VerticalAlignment="Stretch">
        <Grid.ColumnDefinitions>
            <ColumnDefinition x:Name="leftColumn" MinWidth="200" Width="200*"/>
            <ColumnDefinition Width="Auto"/>
            <ColumnDefinition x:Name="centerColumn" MinWidth="300" Width="300*"/>
            <ColumnDefinition Width="Auto"/>
            <ColumnDefinition x:Name="rightColumn" MinWidth="498" Width="498*"/>
        </Grid.ColumnDefinitions>
        <StackPanel x:Name="leftPanel" Grid.Column="0" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Margin="0"/>
        <GridSplitter x:Name="leftSplitter" Grid.Column="1" HorizontalAlignment="Center" VerticalAlignment="Stretch" ResizeBehavior="PreviousAndNext" Width="5" ResizeDirection="Columns"/>
        <StackPanel x:Name="centerPanel" Grid.Column="2" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Margin="0"/>
        <GridSplitter x:Name="rightSplitter" Grid.Column="3" HorizontalAlignment="Center" VerticalAlignment="Stretch" ResizeBehavior="PreviousAndNext" Width="5" ResizeDirection="Columns"/>
        <StackPanel x:Name="rightPanel" Grid.Column="4" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Margin="0" SizeChanged="rightPanel_SizeChanged"/>
    </Grid>

That size changed event is still there only for debug tracing. I output the values of Width to see what is happening. Curiously, after expanding everything and going back to the window's minimum size, the right column width stays larger. But since they all have minwidths and the widths are all star-sized, it works itself out.

I did try to put this back into a binding, but since I'm now storing a string, and the GridLengthConverter is a TypeConverter, not an IValueConverter, it didn't work. I think it may be possible to store the values as GridLengths, though I've reached a point where I'm happy with what I've done. So my load and save are like this:

    protected override void OnSourceInitialized(EventArgs e)
    {
        base.OnSourceInitialized(e);

        //...

        try
        {
            GridLengthConverter converter = new GridLengthConverter();
            leftColumn.Width = (GridLength)converter.ConvertFromString(Settings.Default.MainWindowLeftColumnWidth);
            centerColumn.Width = (GridLength)converter.ConvertFromString(Settings.Default.MainWindowCenterColumnWidth);
            rightColumn.Width = (GridLength)converter.ConvertFromString(Settings.Default.MainWindowRightColumnWidth);

            Trace.WriteLine(string.Format("LOADED Left: {0}, Center: {1}, Right {2}", leftColumn.Width, centerColumn.Width, rightColumn.Width));
        }
        catch (Exception)
        {
            // Fail silently, the worse case is we go with the defaults, it's going to be okay
        }
    }

    protected override void OnClosing(System.ComponentModel.CancelEventArgs e)
    {
        base.OnClosing(e);

        //...

        try
        {
            GridLengthConverter converter = new GridLengthConverter();
            Settings.Default.MainWindowLeftColumnWidth = converter.ConvertToString(leftColumn.Width);
            Settings.Default.MainWindowCenterColumnWidth = converter.ConvertToString(centerColumn.Width);
            Settings.Default.MainWindowRightColumnWidth = converter.ConvertToString(rightColumn.Width);

            Trace.WriteLine(string.Format("SAVED Left: {0}, Center: {1}, Right {2}", Settings.Default.MainWindowLeftColumnWidth, Settings.Default.MainWindowCenterColumnWidth, Settings.Default.MainWindowRightColumnWidth));
        }
        catch (Exception)
        {
            // Fail silently, the worst case is we don't save a little something, it's going to be okay
        }
    }

And that all worked for me. So I'm going to go with it!

EDIT: I later did some refinement to prevent the "curiosity" of the right column staying larger. I now have all panel size changes go to one event handler:

    private void PanelSizeChanged(object sender, SizeChangedEventArgs e)
    {
        // In order to keep the resizing from losing proportionality, we'll force it to be proportional to the current size.
        // Otherwise the right panel edges up and up while the other two remain at their starting 200*/300* values.
        // And when that happens eventually resizing the window only resizes the right panel, not proportionately as it does at the start.
        leftColumn.Width = new GridLength(leftColumn.ActualWidth, GridUnitType.Star);
        centerColumn.Width = new GridLength(centerColumn.ActualWidth, GridUnitType.Star);
        rightColumn.Width = new GridLength(rightColumn.ActualWidth, GridUnitType.Star);
    }
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文