collapsed
SharedSizeGroup及collapsed属性分析及窗口隐藏
tags: wpf C# XAML
由于设计需要,要实现一个类似《WPF揭秘》里的一个布局实例,先把最终需要实现的效果图贴上
图片来源:WPF布局 - ShengM - 博客园
这里面贴出了所有实例代码,但是没有具体分析,由于是新手,折腾了一上午才搞清楚里面的来龙去脉,这里记录一下。
起初需要实现的功能如图中紫色部分一样,当toolbox窗口关闭后可以自动占据其布局位置,同时当Toolbox窗口出现后,其可以自动让出其布局位置。在网上搜了很多,大家都说把需要隐藏对的窗口的Visibility设置为Collapsed就可以实现窗口隐藏,其余窗口自动占位的效果。于是试了一下,发现不行……然后在网上找到了这个布局实例以及其源码,对其进行了分析,找到之前失败的原因。
首先,把之前失败的布局代码贴出来:
<Grid>
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition/>
<RowDefinition/>
<RowDefinition/>
</Grid.RowDefinitions>
<Button Grid.RowSpan="1" content="Button" Margin="0,0,0,0" />
<TabControl Grid.Row="1" Margin="0,0,0,0" TabstripPlacement="Right" Style="{DynamicResource TabControlStyleRotate}">
<TabItem Content="111111" Style="{DynamicResource TabItemStyleRotate}" Header="About"/>
<TabItem Header="About" Content="222222" Style="{DynamicResource TabItemStyleRotate2}"/>
</TabControl>
<Border Grid.Row="2" Background="AliceBlue">
<TextBlock Text="bbbbb bbb bbb bbb bbb bbbba bbbb"/>
</Border>
<Border Grid.Row="3" Visibility="Collapsed">
<TextBlock Text="aaaaa aaaaaa aaaaaaaaa aaaaaaa"/>
</Border>
</Grid>
本来期望第三行能自动填补第四行的空缺的,结果却是这样……
如果是有经验的开发人员……应该一眼就能看出问题,这里主要是给像我一样的新手来说明一下。
首先看一下MSDN对Collapsed的解释:
成员名称 | 说明 |
---|---|
Collapsed | 不显示元素,并且不在布局中为它保留空间 |
hidden | 不显示元素,但是在布局中为元素保留空间 |
Visible | 显示元素 |
所谓不在不居中为它保留空间,是指将该元素的尺寸当成了0,仅此而已。这里我之前就有一点误区,认为该元素的尺寸若为零,则其他元素会自动来对其进行补位,这显然是不成立的,因为在Grid布局中划分了网格区域后,所有空间将只会出现在对应的网格中,除非使用RowSpan这类属性来跨越多个网格部署。(若不指定网格,默认都出现在第一格中)
在布局中什么情况下才会补位?就目前我所知道的是只有在布局中,某个元素拥有“填充满剩余空间”这样的属性时才会自动补位,其实也就是执行其自身的“填充满剩余空间”这个属性。当然这个属性只是我这么称呼他。
目前来说,我常用到的元素中拥有“填充满剩余空间”这个属性的元素是“DockPanel”,还有没有其他的暂时不清楚。对于“StackPanel”这种布局,当把其中的某一个元素进行Collapsed后,其后面的元素会进行补位,但是不会进行伸缩填充,Collapsed前后效果如下图:
这里将bbb进行Collapsed,这时ccc顶替了bbb的区域。但在“DockPanel”布局中,对于最后的那个元素的行为将是填充满剩余空间,所以想要实现本文第一个图中的效果必须使用“DockPanel”布局。下面将第一个图的XAML及C#源码贴出来,然后再对其实现进行分析。
首先是XAML代码:
<Window x:Class="VSUIDemo.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
title="MainWindow">
<DockPanel>
<Menu DockPanel.Dock="Top">
<MenuItem Header="File" />
<MenuItem Header="Edit" />
<MenuItem Header="View" />
<MenuItem Header="Project" />
<MenuItem Header="build" />
<MenuItem Header="Debug" />
<MenuItem Header="Team" />
<MenuItem Header="Tool" />
<MenuItem Header="Test" />
<MenuItem Header="structure" />
<MenuItem Header="Analysis" />
<MenuItem Header="Window" />
<MenuItem Header="Help" />
</Menu>
<StackPanel DockPanel.Dock="Right" Orientation="Horizontal">
<StackPanel.layoutTransform>
<RotateTransform Angle="90" />
</StackPanel.LayoutTransform>
<Button x:Name="toolboxButton"
Content="Toolbox"
MouseEnter="toolboxButton_MouseEnter" />
<Button x:Name="solutionButton"
Margin="2,0"
Content="Solution Explorer"
MouseEnter="solutionButton_MouseEnter" />
</StackPanel>
<Grid Grid.IsSharedSizeScope="True">
<Grid x:Name="layer0Grid"
Panel.ZIndex="0"
MouseEnter="layer0Grid_MouseEnter">
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition />
<RowDefinition />
<RowDefinition />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition />
</Grid.ColumnDefinitions>
<Border Grid.ColumnSpan="2" Background="BlueViolet">
<TextBlock HorizontalAlignment="Center"
VerticalAlignment="Center"
FontSize="36"
Text="Start Page" />
</Border>
<GroupBox Grid.Row="1"
Margin="2"
BorderThickness="2"
Header="recent Projects">
...
</GroupBox>
<GroupBox Grid.Row="1"
Grid.RowSpan="3"
Grid.Column="1"
Margin="2"
BorderThickness="2"
Header="Online Articles">
<listbox>
<ListBoxItem Content="Article #1" />
<ListBoxItem Content="Article #2" />
<ListBoxItem Content="Article #3" />
<ListBoxItem Content="Article #4" />
</ListBox>
</GroupBox>
<GroupBox Grid.Row="2"
Margin="2"
BorderThickness="2"
Header="Getting Started">
...
</GroupBox>
<GroupBox Grid.Row="3"
Margin="2"
BorderThickness="2"
Header="Headlines">
...
</GroupBox>
</Grid>
<Grid x:Name="toolboxLayerGrid" Visibility="Collapsed">
<Grid.ColumnDefinitions>
<ColumnDefinition />
<ColumnDefinition Width="Auto" SharedSizeGroup="ToolboxGroup" />
</Grid.ColumnDefinitions>
<GridSplitter Grid.Column="1"
Width="3"
HorizontalAlignment="Left" />
<Grid x:Name="toolboxGrid"
Grid.Column="1"
Margin="3,0,0,0">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition />
</Grid.RowDefinitions>
<Grid Background="LightBlue">
<Grid.ColumnDefinitions>
<ColumnDefinition />
<ColumnDefinition Width="35" />
</Grid.ColumnDefinitions>
<TextBlock VerticalAlignment="Center"
FontSize="18"
Text="Toolbox"
TextTrimming="CharacterEllipsis" />
<Button x:Name="toolboxLayerPinButton"
Grid.Column="1"
Click="toolboxLayerPinButton_Click">
<Image x:Name="toolboxImage"
Width="24"
Height="24"
Source="Resource/Image/pin_float.png" />
</Button>
</Grid>
<ListBox Grid.Row="1" FontSize="16">
<ListBoxItem Content="Button" />
<ListBoxItem Content="CheckBox" />
<ListBoxItem Content="Label" />
<ListBoxItem Content="ComboBox" />
<ListBoxItem Content="ListBox" />
</ListBox>
</Grid>
</Grid>
<Grid x:Name="solutionLayerGrid" Visibility="Collapsed">
<Grid.ColumnDefinitions>
<ColumnDefinition />
<ColumnDefinition Width="Auto" SharedSizeGroup="SolutionGroup" />
</Grid.ColumnDefinitions>
<GridSplitter Grid.Column="1"
Width="3"
HorizontalAlignment="Left" />
<Grid x:Name="solutionGrid"
Grid.Column="1"
Margin="3,0,0,0">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition />
</Grid.RowDefinitions>
<Grid Background="LightBlue">
<Grid.ColumnDefinitions>
<ColumnDefinition />
<ColumnDefinition Width="35" />
</Grid.ColumnDefinitions>
<TextBlock VerticalAlignment="Center"
FontSize="18"
Text="Solution Explorer"
TextTrimming="CharacterEllipsis" />
<Button x:Name="solutionLayerPinButton"
Grid.Column="1"
Click="solutionLayerPinButton_Click">
<Image x:Name="solutionImage"
Width="24"
Height="24"
Source="Resource/Image/pin_float.png" />
</Button>
</Grid>
<Border Grid.Row="1" Background="White">
<toolbar>
<Image Source="Resource/Image/copy.png" />
<Image Margin="2,0" Source="Resource/Image/paste.png" />
<Image Margin="2,0" Source="Resource/Image/refresh.png" />
</ToolBar>
</Border>
<TreeView Grid.Row="2">
<TreeViewItem Header="My Solution">
<TreeViewItem Header="Project #1" />
<TreeViewItem Header="Project #2" />
<TreeViewItem Header="Project #3" />
</TreeViewItem>
</TreeView>
</Grid>
</Grid>
</Grid>
</DockPanel>
</Window>
一段段来分析吧……
首先<Window></Window>
这个就不说了……
然后是
<DockPanel>
<Menu></Menu>
<StackPanel></StackPanel>
<Grid></Grid>
</DockPanel>
这个里面就是一个基本布局了,布局完了之后大致形状应该是酱紫:
其中1是目录菜单,2是侧边按钮,3是中间的内容,这样,整个内容部分将是一个具有“填充满剩余空间”属性的区域。
其中1和2区域比较简单,就不分析了。
主要来看看3号区域,3号区域代码展开:
<Grid Grid.IsSharedSizeScope="True">
<Grid x:Name="layer0Grid" Panel.ZIndex="0" MouseEnter="layer0Grid_MouseEnter"></Grid>
<Grid x:Name="toolboxLayerGrid" Visibility="Collapsed"></Grid>
<Grid x:Name="solutionLayerGrid" Visibility="Collapsed"></Grid>
<Grid/>
OK,玄机来了……起初一看,这不是操蛋的写法么?!!就我目前的认知水平,如果有一段这样的代码:
<Grid>
<Grid>
<TextBlock Text="1111111111"/>
</Grid>
<Grid>
<TextBlock Text="222222222"/>
</Grid>
<Grid>
<TextBlock Text="33333333"/>
</Grid>
</Grid>
这是什么鬼……显示出来如下:
可是上面的代码明明就是这样写的,为什么那几个框没有这样重叠?这里就是这个布局的一个技巧了,也是这个技巧造就了隐藏Toolbox窗口后,紫色界面立马填充满的“假象”。之所以说是“假象”,下面就来分析。
先来看layer0Grid这个布局:
<Grid x:Name="layer0Grid">
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition />
<RowDefinition />
<RowDefinition />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition />
</Grid.ColumnDefinitions>
<!--元素1-->
<Border Grid.ColumnSpan="2" Background="BlueViolet"></Border>
<!--元素2-->
<GroupBox Grid.Row="1"></GroupBox>
<!--元素3-->
<GroupBox Grid.Row="1" Grid.RowSpan="3" Grid.Column="1"></GroupBox>
<!--元素4-->
<GroupBox Grid.Row="2"></GroupBox>
<!--元素5-->
<GroupBox Grid.Row="3"></GroupBox>
</Grid>
这里只分析主要代码,具体内容比较简单就直接忽略了。这里把3号区域分成4行2列的布局,其中最上面的紫色区域占用2列,布局完后,3号区域应该如图所示:
其中1,2,3,4,5对应的是代码中注释的“元素x”。这个时候原来的3号区域已经被占满了,如果再在3号区域增加toolboxLayerGrid和solutionLayerGrid两个布局,则都将与layer0Grid布局发生重叠,但这里所利用的恰好是布局重叠。再来分析toolboxLayerGrid和solutionLayerGrid,这里只分析toolboxLayerGrid,solutionLayerGrid与toolboxLayerGrid是一个道理。
<Grid x:Name="toolboxLayerGrid" Visibility="Collapsed">
<Grid.ColumnDefinitions>
<ColumnDefinition />
<ColumnDefinition Width="Auto" SharedSizeGroup="ToolboxGroup" />
</Grid.ColumnDefinitions>
<GridSplitter Grid.Column="1" Width="3" HorizontalAlignment="Left" />
<Grid x:Name="toolboxGrid" Grid.Column="1" Margin="3,0,0,0"></Grid>
</Grid>
在这里,可以看到toolboxLayerGrid里面其实将3号区域分成了1行2列的形状,在第二列中有两个东西,一个是GridSplitter,一个是toolboxGrid,toolboxGrid中的Margin左边留3,是为了绘制GridSplitter留的空间,GridSplitter是手动调整窗口大小用的,具体用法这里不单独说。从这里可以看出,toolboxLayerGrid这个布局分成两列的良苦用心,它让真正的toolboxGrid暂用最右边的位置,toolboxGrid的宽度根据toolboxGrid中实际内容的宽度而定,但是3号区域在“DockPanel”中必须填充满所有剩余区域,所以这里把本来1列就可以表述完的toolboxGrid封装成了一个两列的toolboxLayerGrid,toolboxLayerGrid中的第一列的作用就是占位。toolboxLayerGrid与layer0Grid叠加后应为如下视图:
其中蓝色部分为toolboxLayerGrid。一开始,toolboxLayerGrid的属性被设备为了Collapsed,所以,整个蓝色区域没有显示。但一旦将toolbox固定在屏幕上时,蓝色区域将被显示出来,这时,问题来了,layer0Grid将有一部分被toolboxGrid挡住,而不是我们看在第一个图中看到额当toolbox显示后,紫色区域缩回去一截那个效果。如果要实现缩回去的效果就要对layer0Grid做些手脚,这也是前面所说的“假象”的原因。
考虑这样一个问题,若layer0Grid变成这样:
将原来本是两列的layer0Grid,增加一列,变成3列,也就是增加了一个6号区域,这个时候原来的元素1、元素3都会都会自动变小。前提是增加的一列是有固定宽度值的,否则一个没有内容又没有宽度值得列将直接被不显示了。
增加了6号区域后,若6号区域的大小正好等于toolboxGrid,这样就可以让toolboxLayerGrid和layer0Grid叠加后互不干扰了!
再考虑一个更有趣的情况,若6号区域是一个能动态存在的区域,即:当toolboxGrid存在时,layer0Grid才有6号区域,否则layer0Grid没有6号区域,这样不就同时解决了两个问题么?
- 两个Grid叠加遮挡问题
- 紫色区域自动伸缩问题(当有6号区域时,1号区域变小,没有6号区域时1号区域自动占用6号区域的位置)
那么现在唯一的问题就是,如何动态创建和删除6号区域了,这部分代码在C#代码里:
public partial class MainWindow : Window
{
private ColumnDefinition _cloneToolboxGrid;
private ColumnDefinition _cloneSolutionGrid;
private ColumnDefinition _cloneToToolboxLayerGrid;
public MainWindow()
{
Initializecomponent();
_cloneToolboxGrid = new ColumnDefinition { SharedSizeGroup = "ToolboxGroup" };
_cloneSolutionGrid = new ColumnDefinition { SharedSizeGroup = "SolutionGroup" };
_cloneToToolboxLayerGrid = new ColumnDefinition { SharedSizeGroup = "SolutionGroup" };
}
private void toolboxButton_MouseEnter(object sender, MouseEventArgs e)
{
toolboxLayerGrid.Visibility = System.windows.Visibility.Visible;
toolboxLayerGrid.SetValue(Grid.ZIndexProperty, 2);
if (solutionButton.Visibility == System.Windows.Visibility.Visible)
solutionLayerGrid.Visibility = System.Windows.Visibility.Collapsed;
else
solutionLayerGrid.SetValue(Grid.ZIndexProperty, 1);
}
private void solutionButton_MouseEnter(object sender, MouseEventArgs e)
{
solutionLayerGrid.Visibility = System.Windows.Visibility.Visible;
solutionLayerGrid.SetValue(Grid.ZIndexProperty, 2);
if (toolboxButton.Visibility == System.Windows.Visibility.Visible)
toolboxLayerGrid.Visibility = System.Windows.Visibility.Collapsed;
else
toolboxLayerGrid.SetValue(Grid.ZIndexProperty, 1);
}
private void layer0Grid_MouseEnter(object sender, MouseEventArgs e)
{
if (toolboxButton.Visibility == System.Windows.Visibility.Visible)
toolboxLayerGrid.Visibility = System.Windows.Visibility.Collapsed;
if (solutionButton.Visibility == System.Windows.Visibility.Visible)
solutionLayerGrid.Visibility = System.Windows.Visibility.Collapsed;
}
private void toolboxLayerPinButton_Click(object sender, RoutedEventArgs e)
{
if (toolboxButton.Visibility == System.Windows.Visibility.Visible)
{
toolboxImage.Source = new BitmapImage(new Uri("Resource/Image/k1.png", UriKind.Relative));
toolboxButton.Visibility = System.Windows.Visibility.Collapsed;
layer0Grid.ColumnDefinitions.Add(_cloneToolboxGrid);
if (solutionButton.Visibility == System.Windows.Visibility.Collapsed)
toolboxLayerGrid.ColumnDefinitions.Add(_cloneToToolboxLayerGrid);
}
else
{
toolboxImage.Source = new BitmapImage(new Uri("Resource/Image/k1.png", UriKind.Relative));
toolboxButton.Visibility = System.Windows.Visibility.Visible;
toolboxLayerGrid.Visibility = System.Windows.Visibility.Collapsed;
layer0Grid.ColumnDefinitions.Remove(_cloneToolboxGrid);
if (solutionButton.Visibility == System.Windows.Visibility.Collapsed)
toolboxLayerGrid.ColumnDefinitions.Remove(_cloneToToolboxLayerGrid);
}
}
private void solutionLayerPinButton_Click(object sender, RoutedEventArgs e)
{
if (solutionButton.Visibility == System.Windows.Visibility.Visible)
{
solutionImage.Source = new BitmapImage(new Uri("Resource/Image/k1.png", UriKind.Relative));
solutionButton.Visibility = System.Windows.Visibility.Collapsed;
layer0Grid.ColumnDefinitions.Add(_cloneSolutionGrid);
if (toolboxButton.Visibility == System.Windows.Visibility.Collapsed)
toolboxLayerGrid.ColumnDefinitions.Add(_cloneToToolboxLayerGrid);
}
else
{
solutionImage.Source = new BitmapImage(new Uri("Resource/Image/k1.png", UriKind.Relative));
solutionButton.Visibility = System.Windows.Visibility.Visible;
solutionLayerGrid.Visibility = System.Windows.Visibility.Collapsed;
layer0Grid.ColumnDefinitions.Remove(_cloneSolutionGrid);
if (toolboxButton.Visibility == System.Windows.Visibility.Collapsed)
toolboxLayerGrid.ColumnDefinitions.Remove(_cloneToToolboxLayerGrid);
}
}
}
其中layer0Grid.ColumnDefinitions.Add(_cloneToolboxGrid)就是增加6号区域的代码,这里的代码逻辑比较简单,完全是根据布局来的,思路跟6号区域完全一致,这个代码就不再做分析了。
最后来说一下SharedSizeGroup属性。该属性的意思其实就是所有SharedSizeGroup属性值相同的区域拥有相同的大小,大小值为:所有SharedSizeGroup属性相同区域中大小最大的那个区域。
换句话说,若有A和B两个布局,如果他们两个布局的SharedSizeGroup属性相同,都为某值“aaa”(其实“aaa”没有规则,只要符合命名规则就行,你爱叫什么叫什么,只要希望共享size的区域的该名称是一样的就行),则A和B有相同的大小,两个布局谁大就用谁的大小。
所以,这里就可以回答刚刚的一个没有回答的问题,怎么使6号区域与toolboxGrid一样大?现在答案就很简单了,那就是让6号区域和toolboxGrid的SharedSizeGroup属性相同即可,所以layer0Grid.ColumnDefinitions.Add(_cloneToolboxGrid)中会带有 _cloneToolboxGrid参数,这个参数就是toolboxGrid的SharedSizeGroup属性值。
至此,应该差不多把第一个图中演示的实例的实现过程主脉络分析清楚了,剩下的东西就不再分析了。如果有哪里说得不对的,欢迎指正。