WPF当中有许多的布局容器控件,例如<Grid>、<StackPanel>、<WrapPanel>、<DockPanel>、<UniformGrid>。接下来分别介绍一下各个布局容器控件。
布局基础
Grid
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="2*"/>
<RowDefinition/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="2*"/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<Border Grid.Row="0" Grid.Column="0" Background="Red"/>
<Border Grid.Row="0" Grid.Column="1" Background="Yellow"/>
<Border Grid.Row="1" Grid.Column="0" Background="Blue"/>
<Border Grid.Row="1" Grid.Column="1" Background="Green"/>
</Grid>

Grid中的元素还可以跨行和跨列:

StackPanel

StackPanel默认水平排列,具有 Orientation=""属性可以改变排列方向。
WrapPanel

WrapPanel默认水平排列,也具有 Orientation=""属性可以改变排列方向。
DockPanel
DockPanel具有停靠的功能,位于DockPanel中的元素,可以设置它的方向。

UniformGrid
这个容器最大的作用就是在有限的空间里面均分剩余空间。

下面对一个页面进行布局模拟练习:
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="80"/>
<RowDefinition/>
</Grid.RowDefinitions>
<Border Background="#FFECF1"/>
<Grid Grid.Row="1">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="200"/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<Border Background="#FF7F24"/>
<Grid Grid.Column="1" Margin="5">
<Grid.RowDefinitions>
<RowDefinition Height="100"/>
<RowDefinition/>
<RowDefinition/>
</Grid.RowDefinitions>
<Grid Grid.Row="0">
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition/>
<ColumnDefinition/>
<ColumnDefinition/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<Border Margin="5" Grid.Column="0" Background="#1b1c1d"/>
<Border Margin="5" Grid.Column="1" Background="#2AC864"/>
<Border Margin="5" Grid.Column="2" Background="#1b1c1d"/>
<Border Margin="5" Grid.Column="3" Background="#F85A54"/>
<Border Margin="5" Grid.Column="4" Background="#1b1c1d"/>
</Grid>
<Grid Grid.Row="1">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="1.5*"/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<Border Margin="5" Grid.Column="0" Background="#1b1c1d"/>
<Border Margin="5" Grid.Column="1" Background="#2AC864"/>
</Grid>
<Grid Grid.Row="2">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="1.5*"/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<Border Margin="5" Grid.Column="0" Background="#1b1c1d"/>
<Border Margin="5" Grid.Column="1" Background="#F85A54"/>
</Grid>
</Grid>
</Grid>
</Grid>

样式基础
样式负责修饰元素的外观以及行为,可以在样式中定义不同类型元素的属性值集合。
<Window.Resources>
<Style x:Key="BaseStyle" TargetType="Button">
<Setter Property="FontSize" Value="18"/>
<Setter Property="Foreground" Value="White"/>
<Setter Property="Background" Value="Red"/>
</Style>
<Style x:Key="BottonStyle" TargetType="Button" BasedOn="{StaticResource BaseStyle}">
<Setter Property="Content" Value="Botton1"/>
</Style>
</Window.Resources>
<Grid>
<StackPanel>
<Button Background="Blue" Style="{StaticResource BottonStyle}"/>
<Button Style="{StaticResource BottonStyle}"/>
<Button Style="{StaticResource BottonStyle}"/>
</StackPanel>
</Grid>

数据模板
<Grid>
<ListBox x:Name="list">
<ListBoxItem>
<StackPanel Orientation="Horizontal">
<Border Width="10" Height="10" Background="Red"/>
<TextBlock Margin="10,0" Text="Red"/>
</StackPanel>
</ListBoxItem>
<ListBoxItem>
<StackPanel Orientation="Horizontal">
<Border Width="10" Height="10" Background="Blue"/>
<TextBlock Margin="10,0" Text="Red"/>
</StackPanel>
</ListBoxItem>
<ListBoxItem>
<StackPanel Orientation="Horizontal">
<Border Width="10" Height="10" Background="Green"/>
<TextBlock Margin="10,0" Text="Red"/>
</StackPanel>
</ListBoxItem>
</ListBox>
</Grid>

可以看到,这里每一项的构造都是完全相同的,唯一不同的就是它们的数据,因此可以将它抽离出来作为一个模板。
<Grid>
<ListBox x:Name="list">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<Border Width="10" Height="10" Background="{Binding Code}"/>
<TextBlock Margin="10,0" Text="{Binding Name}"/>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</Grid>
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.NetworkInformation;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
namespace WpfApp4 {
/// <summary>
/// MainWindow.xaml 的交互逻辑
/// </summary>
public partial class MainWindow : Window {
public MainWindow() {
InitializeComponent();
List<Color> test = new List<Color>();
test.Add(new Color() { Code = "#FFB6C1", Name = "浅粉红" });
test.Add(new Color() { Code = "#FFC0CB", Name = "粉红" });
test.Add(new Color() { Code = "#DC143C", Name = "猩红" });
test.Add(new Color() { Code = "#FFF0F5", Name = "脸红的淡紫色" });
//LightPink 浅粉红 #FFB6C1 255,182,193
//Pink 粉红 #FFC0CB 255,192,203
//Crimson 猩红 #DC143C 220,20,60
//LavenderBlush 脸红的淡紫色 #FFF0F5 255,240,245
list.ItemsSource = test;
}
public class Color {
public string Code { get; set; }
public string Name { get; set; }
}
}
}

由此就可以看到DataTemplate的作用。继续编写例子:
<Grid>
<DataGrid x:Name = "grid" AutoGenerateColumns="False" CanUserAddRows="False">
<DataGrid.Columns>
<DataGridTextColumn Header="Code" Binding="{Binding Code}"/>
<DataGridTextColumn Header="Name" Binding="{Binding Name}"/>
<DataGridTemplateColumn Header="操作">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<Button Content="删除" />
<Button Content="复制" />
<Button Content="保存" />
</StackPanel>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
</DataGrid>
</Grid>

灵活地使用DataTemplate数据模块,对后续开发有很重要的帮助,当然DataTemplate的应用不止于此,后续还会介绍到。
绑定
使用绑定可以使得控件与控件之间建立一种绑定关系,可以不再为此编写大量的代码来维持。
<Grid>
<StackPanel>
<Slider x:Name="slider" Margin="5" ValueChanged="Slider_ValueChanged"/>
<TextBox x:Name="textbox1" Margin="5" Height="30" TextChanged="textbox1_TextChanged"/>
<TextBox x:Name="textbox2" Margin="5" Height="30"/>
<TextBox x:Name="textbox3" Margin="5" Height="30"/>
</StackPanel>
</Grid>
namespace WpfApp5 {
/// <summary>
/// MainWindow.xaml 的交互逻辑
/// </summary>
public partial class MainWindow : Window {
public MainWindow() {
InitializeComponent();
}
private void Slider_ValueChanged(object sender, RoutedPropertyChangedEventArgs<double> e) {
textbox1.Text = slider.Value.ToString();
textbox2.Text = slider.Value.ToString();
textbox3.Text = slider.Value.ToString();
}
private void textbox1_TextChanged(object sender, TextChangedEventArgs e) {
if (double.TryParse(textbox1.Text, out double result))
slider.Value = result;
}
}
}

此时代码实现了拖动滑块,下方三个框里的数据会同步变化。对下方任意一个框里的数据进行修改,上方滑块的位置也会同步变化。为了实现这个功能,代码变得特别的冗余,可以使用绑定来简化。
<Grid>
<StackPanel>
<Slider x:Name="slider" Margin="5"/>
<TextBox Text="{Binding ElementName=slider,Path=Value}" x:Name="textbox1" Margin="5" Height="30"/>
<TextBox Text="{Binding ElementName=slider,Path=Value}" x:Name="textbox2" Margin="5" Height="30"/>
<TextBox Text="{Binding ElementName=slider,Path=Value}" x:Name="textbox3" Margin="5" Height="30"/>
</StackPanel>
</Grid>
此时就不需要具体的实现代码了,使用绑定就可以完成两个控件的双向绑定。