我读了 Robert Martin 并试图理解重构的所有复杂之处。
如果使用 C# 代码一切都或多或少地清晰,并且代码逐渐开始令人赏心悦目,那么 XAML 则一切都令人难过。标记很繁琐且难以阅读。很明显,很多与 xml-“遗传”有关。xml是多余的,不太顺眼,但还是。什么是重构 xaml 的方式和谁使用什么?
理想情况下,我想摆脱十级层次结构并获得 5-7 行的简短“方法”(就像我在 c# 代码中所做的那样)。
这是我上一个 WPF 应用程序的其中一个窗口的标记(所有 320 行碎肉):
<Window x:Class="InfoReceiverB.Views.EditorWindow"
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:InfoReceiverB.Views"
mc:Ignorable="d"
Title="Менеджер скриптов" Icon="/papers2.ico"
WindowStartupLocation="CenterScreen"
Height="700" Width="1100">
<Grid Margin="3">
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="2*"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="3*"/>
</Grid.RowDefinitions>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<ListBox Padding="2.5" Margin="3"
ItemsSource="{Binding Repo.Queries}"
SelectedItem="{Binding SelectedQuery}">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Id}" Margin="0,0,2,0"/>
<TextBlock Text="{Binding Caption}"/>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
<StackPanel Grid.Column="1"
Margin="0,3,3,3">
<Button Content="+"
Padding="5" Margin="0,0,0,3"
Command="{Binding AddQueryCommand}"/>
<Button Content="-"
Padding="5" Margin="0,0,0,3"
Command="{Binding DelQueryCommand}"
CommandParameter="{Binding SelectedQuery}">
<Button.Style>
<Style TargetType="Button">
<Style.Triggers>
<Trigger Property="CommandParameter" Value="{x:Null}">
<Setter Property="IsEnabled" Value="False"/>
</Trigger>
</Style.Triggers>
</Style>
</Button.Style>
</Button>
</StackPanel>
</Grid>
<GridSplitter Grid.Row="1" Height="3"
HorizontalAlignment="Stretch"
Background="DarkGray" Margin="3,0"/>
<Grid Grid.Row="2" DataContext="{Binding SelectedQuery}">
<Grid.Style>
<Style TargetType="Grid">
<Style.Triggers>
<Trigger Property="DataContext" Value="{x:Null}">
<Setter Property="IsEnabled" Value="False"/>
</Trigger>
</Style.Triggers>
</Style>
</Grid.Style>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<TextBlock Text="{Binding Id}"
Padding="5" Margin="3,3,0,3"/>
<TextBox Grid.Column="1"
Text="{Binding Caption}"
Padding="5" Margin="0,3,3,3"
AcceptsReturn="True"/>
<TextBox Grid.Row="1" Grid.ColumnSpan="2"
Text="{Binding Body}"
FontFamily="Consolas" FontSize="14"
Padding="5" Margin="3,0,3,3"
AcceptsReturn="True" AcceptsTab="True"
HorizontalScrollBarVisibility="Auto"
VerticalScrollBarVisibility="Auto"/>
</Grid>
</Grid>
<GridSplitter Grid.Column="1" Width="3"
HorizontalAlignment="Stretch"
Background="DarkGray" Margin="0,3"/>
<Grid Grid.Column="2">
<Grid.RowDefinitions>
<RowDefinition Height="2*"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="3*"/>
</Grid.RowDefinitions>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<ListBox Padding="2.5" Margin="3"
ItemsSource="{Binding Repo.QueryPacks}"
SelectedItem="{Binding SelectedQueryPack}">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Id}" Margin="0,0,2,0"/>
<TextBlock Text="{Binding Caption}"/>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
<StackPanel Grid.Column="1"
Margin="0,3,3,3">
<Button Content="+"
Padding="5" Margin="0,0,0,3"
Command="{Binding AddQueryPackCommand}"/>
<Button Content="-"
Padding="5" Margin="0,0,0,3"
Command="{Binding DelQueryPackCommand}"
CommandParameter="{Binding SelectedQueryPack}">
<Button.Style>
<Style TargetType="Button">
<Style.Triggers>
<Trigger Property="CommandParameter" Value="{x:Null}">
<Setter Property="IsEnabled" Value="False"/>
</Trigger>
</Style.Triggers>
</Style>
</Button.Style>
</Button>
</StackPanel>
</Grid>
<GridSplitter Grid.Row="1" Height="3"
HorizontalAlignment="Stretch"
Background="DarkGray" Margin="3,0"/>
<Grid Grid.Row="2" DataContext="{Binding SelectedQueryPack}">
<Grid.Style>
<Style TargetType="Grid">
<Style.Triggers>
<Trigger Property="DataContext" Value="{x:Null}">
<Setter Property="IsEnabled" Value="False"/>
</Trigger>
</Style.Triggers>
</Style>
</Grid.Style>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition/>
</Grid.RowDefinitions>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<TextBlock Text="{Binding Id}"
Padding="5" Margin="3,3,0,3"/>
<TextBox Grid.Column="1"
Text="{Binding Caption}"
Padding="5" Margin="0,3,3,3"/>
</Grid>
<Grid Grid.Row="1">
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<ListBox Name="list1" Padding="2.5" Margin="3"
ItemsSource="{Binding DataContext.Repo.Queries,
RelativeSource={RelativeSource FindAncestor, AncestorType=Window}}">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Id}" Margin="0,0,2,0"/>
<TextBlock Text="{Binding Caption}"/>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
<StackPanel Grid.Column="1"
Margin="0,3" VerticalAlignment="Center">
<Button Content=">"
Padding="5" Margin="0,0,0,3"
Command="{Binding DataContext.AddQueryToPackCommand,
RelativeSource={RelativeSource FindAncestor, AncestorType=Window}}"
CommandParameter="{Binding SelectedItem, ElementName=list1}">
<Button.Style>
<Style TargetType="Button">
<Style.Triggers>
<Trigger Property="CommandParameter" Value="{x:Null}">
<Setter Property="IsEnabled" Value="False"/>
</Trigger>
</Style.Triggers>
</Style>
</Button.Style>
</Button>
<Button Content="<"
Padding="5" Margin="0,7,0,3"
Command="{Binding DataContext.DelQueryFromPackCommand,
RelativeSource={RelativeSource FindAncestor, AncestorType=Window}}"
CommandParameter="{Binding SelectedItem, ElementName=list2}">
<Button.Style>
<Style TargetType="Button">
<Style.Triggers>
<Trigger Property="CommandParameter" Value="{x:Null}">
<Setter Property="IsEnabled" Value="False"/>
</Trigger>
</Style.Triggers>
</Style>
</Button.Style>
</Button>
<Button Content="^"
Padding="5" Margin="0,0,0,3"
Command="{Binding DataContext.UpQueryInPackCommand,
RelativeSource={RelativeSource FindAncestor, AncestorType=Window}}"
CommandParameter="{Binding SelectedItem, ElementName=list2}">
<Button.Style>
<Style TargetType="Button">
<Style.Triggers>
<Trigger Property="CommandParameter" Value="{x:Null}">
<Setter Property="IsEnabled" Value="False"/>
</Trigger>
</Style.Triggers>
</Style>
</Button.Style>
</Button>
<Button Content="v"
Padding="5" Margin="0,0,0,3"
Command="{Binding DataContext.DownQueryInPackCommand,
RelativeSource={RelativeSource FindAncestor, AncestorType=Window}}"
CommandParameter="{Binding SelectedItem, ElementName=list2}">
<Button.Style>
<Style TargetType="Button">
<Style.Triggers>
<Trigger Property="CommandParameter" Value="{x:Null}">
<Setter Property="IsEnabled" Value="False"/>
</Trigger>
</Style.Triggers>
</Style>
</Button.Style>
</Button>
</StackPanel>
<ListBox Grid.Column="2" Name="list2"
Padding="2.5" Margin="3"
ItemsSource="{Binding Queries}">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Id}" Margin="0,0,2,0"/>
<TextBlock Text="{Binding Caption}"/>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</Grid>
</Grid>
</Grid>
</Grid>
<UniformGrid Grid.Row="1" HorizontalAlignment="Right"
Rows="1">
<Button Grid.Column="1"
Content="Применить" IsDefault="True"
Padding="5" Margin="0,3,3,3"
Command="{Binding ApplyCommand}"
Click="ApplyButtonClick"/>
<Button Grid.Column="2"
Content="Отмена" IsCancel="True"
Padding="5" Margin="0,3,3,3"/>
</UniformGrid>
</Grid>
</Window>
我很高兴收到任何建议和评论,尤其是示例
通常的替代方法是将公共逻辑部分放在单独的代码中。
如果您的标记部分具有独立的含义、独立的功能,则应将它们移至单独的
UserControl
. 此外,如果这些片段是重复的,您可以重复使用您的UserControl
. (但即使没有这个,如果有一个独立的意义,你需要将它分离到一个新的控件中——就像过程一样。)如果部分重复,但不完全重复,使你的部分可UserControl
参数化以定制其外观和行为。这是在命令式编程中使用子例程(函数/过程)的完整模拟。如果没有UserControl
',您的代码将成为不受支持的整体,就像没有过程的命令式代码一样。接下来,如果您的控件具有公共属性,请将它们放在样式中。它可能比只在所有控件中编写属性要复杂一些,但是编写良好的样式可以重用、继承和修改。您可以创建样式层次结构并将它们应用于您的控件,以相同的方式修改它们的行为。这类似于命令式编程中的 AOP。
PS:这不会给您 10 行的部分,因为 XML 仍然非常冗长并且不允许在一个文件中包含多个实体。我认为一个现实的目标可能是每个文件 30-50 行。