UI佈局

WPF作爲專門的用戶界面技術,佈局功能是它的核心功能之一。友好的用戶界面和良好的用戶體驗離不開設計精良的佈局。日常工作中,WPF設計師工作量最大的兩部分就是佈局和動畫,除了點綴性的動畫外,大部分動畫也是佈局間的轉換,UI佈局的重要性可見一斑。佈局是靜態的,動畫是動態的,用戶體驗就是用戶在這動靜之中與軟件功能產生交互時的感受。

每個佈局元素都有自己的特點,有優點也有缺點。


1.佈局元素總體介紹


Panel有個附加屬性:
ZIndex:獲取或設置一個值,該值表示元素在 z 平面上出現的順序。如果一個子元素的 ZIndex 值較高,則表明此元素將顯示在具有較低值的另一個子元素之上。

1.1  Canvas

Canvas是最基本的面板,Canvas佈局和Windows Form佈局基本一致。Windows Form開發時,通過設置控件的Left和Top來定位,Canvas佈局中,可以通過設置Canvas的附加屬性:Canvas.Top,Canvas.Left,Canvas.Bottom ,Canvas.Right來定位,但是Canvas只允許設置一個橫向和縱向成對的座標。附加屬性 Canvas.Top 或 Canvas.Left 將優先於 Canvas.Bottom 或 Canvas.Right 屬性。
<Canvas>
	<Label Canvas.Left="10" Canvas.Right="10" Canvas.Top="0" Content="Left:10,Top:0,Right:10" Background="Gray" />
	<Label Canvas.Right="10" Canvas.Top="0" Content="Right:10,Top:0" Background="Gray" />
</Canvas>
效果

1.2 StackPanel

StackPanel佈局將元素排列成一行,可縱向可橫向,通過Orientation屬性來控制。
關於FlowDirection,獲取或設置文本和其他user interface (UI) 元素在控制它們佈局的任何父元素中的流動方向,默認都是LeftToRight佈局,假如爲RightToLeft,StaticPanel的Orientation="Horizontal"時,StaticPane中的項也是從左向右佈局的。
<!--添加ScrollViewer以至出現滾動條-->
<ScrollViewer VerticalScrollBarVisibility="Auto" HorizontalScrollBarVisibility="Auto">
	<StackPanel Orientation="Horizontal" FlowDirection="RightToLeft">
		<Label Content="item1" Width="100" Height="50" Background="Gray" Margin="5" HorizontalAlignment="Left" FlowDirection="LeftToRight" />
		<Label Content="item2" Width="100" Height="50" Background="Gray" Margin="5" HorizontalAlignment="Left" />
		<Label Content="item3" Width="100" Height="50" Background="Gray" Margin="5" HorizontalAlignment="Left" />
		<Label Content="item4" Width="100" Height="50" Background="Gray" Margin="5" HorizontalAlignment="Left" />
		<Label Content="item5" Width="100" Height="50" Background="Gray" Margin="5" HorizontalAlignment="Left" />
		<Label Content="item6" Width="100" Height="50" Background="Gray" Margin="5" HorizontalAlignment="Left" />
	</StackPanel>
</ScrollViewer>
效果

1.3 WrapPanel

WrapPanel和 StackPanel基本類似,區別在於元素一行(列)排列不下時,會另起新行(列)。
主要屬性:
Orientation:與StaticPanel的功能一致,控制排列方向。
ItemHeightItemWidth:控制子元素的高度和寬度。
<WrapPanel Orientation="Horizontal" ItemHeight="50" ItemWidth="140" FlowDirection="RightToLeft">
	<Border BorderBrush="Red" BorderThickness="1">
		<Label Content="item1" Width="100" Height="50" Background="Gray" Margin="5" HorizontalAlignment="Left" FlowDirection="LeftToRight" />
	</Border>
	<Label Content="item2" Width="100" Height="50" Background="Gray" Margin="5" HorizontalAlignment="Left" />
	<Label Content="item3" Width="100" Height="50" Background="Gray" Margin="5" HorizontalAlignment="Left" />
	<Label Content="item4" Width="100" Height="50" Background="Gray" Margin="5" HorizontalAlignment="Left" />
	<Label Content="item5" Width="100" Height="50" Background="Gray" Margin="5" HorizontalAlignment="Left" />
	<Label Content="item6" Width="100" Height="50" Background="Gray" Margin="5" HorizontalAlignment="Left" />
</WrapPanel>
效果

1.4 DockPanel

DockPanel可以使子元素停靠在面板的某一邊上,然後拉伸元素以填滿全部寬度或高度。默認最後一個元素填充餘下的空間。
附加屬性:
Dock:Left(默認)、Top、Right和Buttom。
依賴屬性:
LastChildFill:指示 DockPanel 中的最後一個子元素是否拉伸以填滿剩餘可用空間。
<DockPanel LastChildFill="False">
	<Label Content="Left" DockPanel.Dock="Left" Background="Gray" />
	<Label Content="Top" DockPanel.Dock="Top" Background="Blue" />
	<Label Content="Right" DockPanel.Dock="Right" Background="LightBlue" />
	<Label Content="Bottom" DockPanel.Dock="Bottom" Background="LightGray" />
	<!--默認DockPanel.Dock="Left"-->
	<Label Content="Last" Background="LightSkyBlue"/>
</DockPanel>
效果

1.5 Grid

Grid是最通用的面板,允許在一個多行多列的表中排列子元素。如需要顯示Grid表格線,使用ShowGridLines屬性。
常用的4個附加屬性:
Column:子元素在Grid中所在的列。
ColumnSpan:子元素在Grid中跨越的列數。
Row:子元素在Grid中所在的行。
RowSpan:子元素在Grid中跨越的行數數。

1.5.1 Grid行和列的尺寸(GridLength)

  • 絕對尺寸:固定的寬度或高度。Grid改變時,對應的RowDefinition和ColumnDefinition尺寸不變。
單位:

英文名

中文名

簡寫

換算

Pixel

像素

px(默認單位,可省略)

 

Inch

英寸

in

1in=96px

Centimeter

釐米

cm

1cm=(96/2.54)px

Point

pt

1pt=(96/72)px

  • 自動尺寸:對應的RowDefinition和ColumnDefinition尺寸由其子元素決定,爲子元素所需空間。
  • 比例尺寸:將Grid剩餘空間(Grid中空間-絕對尺寸和自動尺寸所佔空間)按比例分配。
//絕對尺寸
GridLength gl1 = new GridLength(100);
GridLength gl2 = new GridLength(100,GridUnitType.Pixel);
//自動尺寸
GridLength gl3 = new GridLength(0,GridUnitType.Auto);
GridLength gl4 = GridLength.Auto;
//比例尺寸
GridLength gl5 = new GridLength(1, GridUnitType.Star);

1.5.2 GridSplitter的使用

GridSplitter是Grid 控件的列或行之間重新分配空間的控件。GridSplitter 可能與包含其他內容的行或列重疊,或者它本身可能會佔據一行或一列。
GridSplitter的HorizontalAlignment默認值爲Right,VerticalAlignment的默認值爲Stretch。要使GridSplitter顯示,必須在某一方向上是Stretch,不然就是一個小圓點;另外還需要有一定的高度或寬度。如果 HorizontalAlignment 和 VerticalAlignment 屬性的設置沒有實現所需的 GridSplitter 行爲,則可以更改 ResizeDirection 和 ResizeBehavior 屬性的設置。
GridSplitter 可能會被 Grid 的 Children 集合中包含的其他對象遮蓋。通過設置Panel.ZIndex附加屬性來控制z方向的位置。

1.5.3 共享行和列的尺寸

ColumnDefinition和RowDefinition有個SharedSizeGroup屬性,允許多行或多列與其他行或列保持一樣的長度,即使其中的行或列在運行時(如使用GridSplitter)改變也是如此。SharedSizeGroup可以被設置爲一個大小寫敏感的字符串值,表示某個組的名稱,其他擁有相同組名的行或列將保持同步。
SharedSizeGroup 屬性值必須符合下列規則:
  • 不得爲空;
  • 必須只包含字母、數字和下劃線字符;
  • 不得以數值開頭。
注意點:
  • 如需SharedSizeGroup共享大小生效,需設置IsSharedSizeScope屬性,該屬性是Grid的依賴屬性,也是附加屬性。由於一個共享組可以在多個Grid間使用,爲避免可能的名稱衝突(也爲了減少需要遍歷的邏輯樹數量),同一個SharedSizeGroup必須在一個IsSharedSizeScope爲true的共同的父元素下使用。
  • 參與大小共享的列和行不遵循 Star 大小(比例尺寸)調整。 在大小共享方案中,Star 大小調整將作爲 Auto 處理。
  • 如果在某個資源模板內將 IsSharedSizeScope 設置爲 true,並將 SharedSizeGroup 定義爲在該模板的外部,則 Grid 大小共享不起作用。
<StackPanel Orientation="Vertical" Grid.IsSharedSizeScope="True">
	<Grid ShowGridLines="True">
		<Grid.ColumnDefinitions>
			<!--絕對尺寸-->
			<ColumnDefinition Width="100" />
			<!--自動尺寸-->
			<ColumnDefinition Width="auto" />
			<!--比例尺寸-->
			<ColumnDefinition Width="1*" />
			<ColumnDefinition Width="3*" />
		</Grid.ColumnDefinitions>
		<Grid.RowDefinitions>
			<RowDefinition Height="1in" SharedSizeGroup="myshare" />
			<RowDefinition Height="96" />
			<RowDefinition />
			<RowDefinition />
		</Grid.RowDefinitions>
		<Label Grid.Column="0" Grid.Row="1" Content="長度固定" Background="Gray"/>
		<Label Grid.Column="1" Grid.Row="1" Content="長度不定,我有多寬列有多寬" Background="LightBlue"/>
		<Label Grid.Column="2" Grid.Row="1" Content="長度不定,佔據餘下寬帶的1/4" Background="LightGray"/>
		<GridSplitter Height="5" Grid.ColumnSpan="3" Grid.Row="1" Background="Red" 
				  VerticalAlignment="Bottom" HorizontalAlignment="Stretch"
				  />
		<GridSplitter Height="5" Grid.Column="3" Grid.Row="1" Background="Black" VerticalAlignment="Bottom" HorizontalAlignment="Stretch"/>
		<GridSplitter Width="5" Grid.Column="1" Grid.Row="1" Background="Blue"
				  VerticalAlignment="Stretch" HorizontalAlignment="Right"
				  ResizeBehavior="BasedOnAlignment"/>
		<Label Grid.Row="3" Content="與第一行共享高度" Background="Gray" />
		
		<Label Content="share row" />
		<!--拖動此GridSplitter,查看共享行效果-->
		<GridSplitter VerticalAlignment="Bottom" HorizontalAlignment="Stretch" Height="5" Background="Red" 
					  ToolTip="拖動此GridSplitter,查看共享行效果"/>
	</Grid>
	<Grid ShowGridLines="True">
		<Grid.RowDefinitions>
			<RowDefinition SharedSizeGroup="myshare" />
			<RowDefinition Height="100" />
		</Grid.RowDefinitions>
		<Label Content="share row" />
	</Grid>
</StackPanel>
效果

1.6 VirtualizingPanel

VirtualizingPanel爲虛擬化其子數據集合的 Panel 元素提供一個框架。
詳細後續介紹。

1.7 TabPanel

TabPanel處理 TabControl 上的 TabItem 對象的佈局。與WrapPanel類似,僅支持從上至下的換行,換行發生時會平均拉伸元素。

1.8 ToolBarOverflowPanel

ToolBarOverflowPanel用來排列溢出的 ToolBar 項。ToolBar 上可能沒有足夠的空間顯示其所有項。 發生這種情況時,可以將 ToolBar 上的項放入 ToolBarOverflowPanel,它是在 ToolBar 的 ControlTemplate 中指定的。與WrapPanel相似,僅支持從左到右、從上至下的換行。
主要屬性:
WrapWidth:在項溢出到下一行之前獲取或設置溢出 ToolBar 的建議寬度。

1.9 ToolBarPanel

ToolBarPanel派生自StackPanel,用於 ToolBar 中排列 ToolBar 項 。

1.10 UniformGrid

UniformGrid提供一種在所有單元格大小相同的網格中安排內容的方法。只需要定義行數和列數,UniformGrid會自動排列子元素。
主要屬性:
Columns:網格的列數。
Rows:網格的行數。
FirstColumn:網格第一行中前導空白單元格的數量。
<!--定義4列-->
<UniformGrid Columns="4" FirstColumn="1">
	<Label Content="item1" Background="Gray" Margin="5" HorizontalAlignment="Stretch" FlowDirection="RightToLeft" />
	<Label Content="item2" Background="Gray" Margin="5" HorizontalAlignment="Stretch" />
	<Label Content="item3" Background="Gray" Margin="5" HorizontalAlignment="Stretch" />
	<Label Content="item4" Background="Gray" Margin="5" HorizontalAlignment="Stretch" />
	<Label Content="item5" Background="Gray" Margin="5" HorizontalAlignment="Stretch" />
	<Label Content="item6" Background="Gray" Margin="5" HorizontalAlignment="Stretch" />
</UniformGrid>
效果

2. 自定義佈局面板

要掌握自定義佈局,必須瞭解佈局的兩個階段。實際上確定控件最佳尺寸的經歷了兩個階段,第1個階段爲測量(Measure)階段,即父元素詢問子元素所期望的尺寸,從而確定自身的尺寸;第2階段爲佈置(Arrange)階段,在這個期間每個父元素會告知子元素的尺寸和位置。
從編程模型來看,具體到兩個重載函數,即MeasureOverride和ArrangeOverride。
自定義面板代碼:
//斜對角排列子元素
public class MyPanel : Panel
{
	public MyPanel()
		: base()
	{
	}

	//測量(Measure)階段,即父元素詢問子元素所期望的尺寸,從而確定自身的尺寸
	//MeasureOverride傳遞的參數爲Size類型,實際是上一級父元素告知當前元素可分配的空間(availableSize);返回的參數Size類型,是該元素所期望的空間(desiredSize)
	protected override Size MeasureOverride(Size availableSize)
	{
		double maxWidth = 0.0;
		double maxHeight = 0.0;
		double sumHeight = 0.0;
		double l = Math.Sqrt(availableSize.Height * availableSize.Height + availableSize.Width * availableSize.Width);
		double heightratio = availableSize.Height / l;
		double widthratio = availableSize.Width / l;
		foreach (UIElement child in InternalChildren)
		{
			child.Measure(availableSize);
			maxWidth = Math.Max(child.DesiredSize.Width, maxWidth);
			maxHeight = Math.Max(child.DesiredSize.Height, maxHeight);
			sumHeight += child.DesiredSize.Height;
		}
		Size ideal = new Size(maxWidth * widthratio + sumHeight * heightratio, maxWidth * heightratio + sumHeight * widthratio);
		Size desired = ideal;
		if (!double.IsInfinity(availableSize.Width))
		{
			if (availableSize.Width < desired.Width)
				desired.Width = availableSize.Width;
		}
		if (!double.IsInfinity(availableSize.Height))
		{
			if (availableSize.Height < desired.Height)
				desired.Height = availableSize.Height;
		}
		return desired;
	}

	//佈置(Arrange)階段,在這個期間每個父元素會告知子元素的尺寸和位置
	//ArrangeOverride傳遞和返回的參數同樣是Size類型,傳遞的參數指定是該元素擺放所用的尺寸(finalSize);返回參數同爲該元素及其子元素所佔用的尺寸。
	protected override Size ArrangeOverride(Size finalSize)
	{
		Rect layoutRect = new Rect(0, 0, finalSize.Width, finalSize.Height);
		double angle = -1 * Math.Atan(finalSize.Height / finalSize.Width) * 180 / Math.PI;
		double l = Math.Sqrt(finalSize.Height * finalSize.Height + finalSize.Width * finalSize.Width);
		double heightratio = finalSize.Height / l;
		double widthratio = finalSize.Width / l;
		double left = 0.0;
		double top = 0.0;
		double maxWidth = 0.0;
		foreach (UIElement child in InternalChildren)
		{
			maxWidth = Math.Max(child.DesiredSize.Width, maxWidth);
		}
		top = maxWidth * heightratio;
		foreach (UIElement child in InternalChildren)
		{
			Point childLocation = new Point(left + (maxWidth - child.DesiredSize.Width) * widthratio / 2, top);
			left += (layoutRect.Width - maxWidth * widthratio) / InternalChildren.Count;
			top += (layoutRect.Height - maxWidth * heightratio) / InternalChildren.Count;

			//旋轉中心點沒定好,顯示效果不好
			//child.RenderTransform = new RotateTransform
			//(angle, childLocation.X, childLocation.Y);
			child.Arrange(new Rect(childLocation, child.DesiredSize));
		}
		return finalSize;
	}
}
使用自定義面板:
<local:MyPanel>
	<Button Background="#00000000" Width="100">1</Button>
	<Button Background ="#FFFFCCFF" Width="150">2</Button>
	<Button Background ="#FFFF9BFF" Width="120">3</Button>
	<Button Background ="#FFFF00FF" Width="50">4</Button>
	<Button Background="#FFFFCCFF" Width="80">5</Button>
</local:MyPanel>
效果



作者:FoolRabbit
出處:http://blog.csdn.net/rabbitsoft_1987
歡迎任何形式的轉載,未經作者同意,請保留此段聲明!

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章