The Rubberduck 2.0 Code Inspections UI was recently redesigned (again!), this time featuring a GroupingGrid
control (thanks @BrunoCosta!), and what appears to be a dynamic grouping:
Resources
The UserControl
's resources include two CollectionViewSource
definitions, which determine the sorting and grouping rules for the user-selectable grouping styles.
<UserControl x:Class="Rubberduck.UI.CodeInspections.InspectionResultsControl"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:inspections="clr-namespace:Rubberduck.Inspections"
xmlns:resx="clr-namespace:Rubberduck.UI"
xmlns:codeInspections="clr-namespace:Rubberduck.UI.CodeInspections"
xmlns:controls="clr-namespace:Rubberduck.UI.Controls"
xmlns:componentModel="clr-namespace:System.ComponentModel;assembly=WindowsBase"
ResxExtension.DefaultResxName="Rubberduck.UI.RubberduckUI"
Language="{UICulture}"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300"
d:DataContext="{d:DesignInstance codeInspections:InspectionResultsViewModel}">
<UserControl.Resources>
<BooleanToVisibilityConverter x:Key="BoolToVisibility"/>
<Style x:Key="LinkButton" TargetType="Button" BasedOn="{StaticResource ResourceKey={x:Type Button}}">
<!-- irrelevant -->
</Style>
<codeInspections:InspectionSeverityImageSourceConverter x:Key="SeverityIconConverter" />
<codeInspections:InspectionImageSourceConverter x:Key="InspectionIconConverter" />
<codeInspections:InspectionTypeConverter x:Key="InspectionTypeConverter" />
<Style x:Key="IconStyle" TargetType="Image">
<Setter Property="Height" Value="16" />
<Setter Property="Width" Value="16" />
<Setter Property="Margin" Value="4" />
</Style>
<CollectionViewSource x:Key="ResultsByInspectionType" Source="{Binding Results}">
<CollectionViewSource.SortDescriptions>
<componentModel:SortDescription PropertyName="Inspection.Name"/>
</CollectionViewSource.SortDescriptions>
<CollectionViewSource.GroupDescriptions>
<PropertyGroupDescription PropertyName="Inspection" Converter="{StaticResource InspectionTypeConverter}" />
</CollectionViewSource.GroupDescriptions>
</CollectionViewSource>
<CollectionViewSource x:Key="ResultsByModule" Source="{Binding Results}">
<CollectionViewSource.SortDescriptions>
<componentModel:SortDescription PropertyName="QualifiedSelection.QualifiedName.ComponentName"/>
</CollectionViewSource.SortDescriptions>
<CollectionViewSource.GroupDescriptions>
<PropertyGroupDescription PropertyName="QualifiedSelection.QualifiedName" />
</CollectionViewSource.GroupDescriptions>
</CollectionViewSource>
</UserControl.Resources>
Content
So, here's the actual control:
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="30"/>
<RowDefinition Height="*" MinHeight="64" />
<RowDefinition Height="5"/>
<RowDefinition Height="Auto" MinHeight="48"/>
</Grid.RowDefinitions>
<Border Grid.Row="0" Grid.RowSpan="3" Background="#FFEEF5FD" />
<ToolBar Grid.Row="0">
<Button Command="{Binding RefreshCommand}">
<Image Height="16" Source="../../Resources/arrow-circle-double.png" />
</Button>
<Separator />
<Menu>
<MenuItem VerticalAlignment="Center"
Header="{Resx ResxName=Rubberduck.UI.RubberduckUI, Key=Fix}"
ItemsSource="{Binding SelectedItem.QuickFixes}">
<MenuItem.Icon>
<Image Height="16" Source="../../Resources/tick.png" />
</MenuItem.Icon>
<MenuItem.ItemContainerStyle>
<Style TargetType="{x:Type MenuItem}">
<Setter Property="Command" Value="{Binding Path=DataContext.QuickFixCommand, RelativeSource={RelativeSource FindAncestor, AncestorType=Menu, AncestorLevel=1}}" />
<Setter Property="CommandParameter" Value="{Binding}" />
<Setter Property="Header" Value="{Binding Description}" />
</Style>
</MenuItem.ItemContainerStyle>
</MenuItem>
</Menu>
<Menu>
<MenuItem Header="Grouping">
<MenuItem.Icon>
<Image Height="16" Source="../../Resources/Microsoft/PNG/GroupBy_284_32.png" />
</MenuItem.Icon>
<MenuItem x:Name="GroupByInspectionType" VerticalAlignment="Center" Header="By inspection type" IsChecked="True"
IsCheckable="True" controls:MenuItemGroup.GroupName="GroupingStyle" />
<MenuItem x:Name="GroupByModule" VerticalAlignment="Center" Header="By module"
IsCheckable="True" controls:MenuItemGroup.GroupName="GroupingStyle" />
</MenuItem>
</Menu>
<Separator />
<Button Command="{Binding CopyResultsCommand}">
<Image Height="16" Source="../../Resources/document-copy.png" />
</Button>
</ToolBar>
<controls:GroupingGrid Grid.Row="1" x:Name="ResultsByInspectionTypeGrid"
ShowGroupingItemCount="True"
SelectedItem="{Binding SelectedItem}"
ItemsSource="{Binding Source={StaticResource ResultsByInspectionType}}"
Visibility="{Binding IsChecked, ElementName=GroupByInspectionType, Converter={StaticResource BoolToVisibility}}">
<DataGrid.Columns>
<DataGridTemplateColumn Header="{Resx ResxName=Rubberduck.UI.RubberduckUI, Key=CodeInspectionResults_Type}">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate DataType="inspections:ICodeInspectionResult">
<Image Source="{Binding Inspection, Converter={StaticResource InspectionIconConverter}}" Height="16" />
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
<DataGridTextColumn Header="{Resx ResxName=Rubberduck.UI.RubberduckUI, Key=CodeInspectionResults_Location}" Binding="{Binding QualifiedSelection.QualifiedName}" />
<DataGridTextColumn Header="{Resx ResxName=Rubberduck.UI.RubberduckUI, Key=CodeInspectionResults_Issue}" Binding="{Binding Description}" />
</DataGrid.Columns>
</controls:GroupingGrid>
<controls:GroupingGrid Grid.Row="1" x:Name="ResultsByModuleGrid"
ShowGroupingItemCount="True"
SelectedItem="{Binding SelectedItem}"
ItemsSource="{Binding Source={StaticResource ResultsByModule}}"
Visibility="{Binding IsChecked, ElementName=GroupByModule, Converter={StaticResource BoolToVisibility}}">
<DataGrid.Columns>
<DataGridTemplateColumn Header="{Resx ResxName=Rubberduck.UI.RubberduckUI, Key=CodeInspectionResults_Type}">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate DataType="inspections:ICodeInspectionResult">
<Image Source="{Binding Inspection, Converter={StaticResource InspectionIconConverter}}" Height="16" />
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
<DataGridTextColumn Header="{Resx ResxName=Rubberduck.UI.RubberduckUI, Key=CodeInspectionResults_Location}" Binding="{Binding QualifiedSelection.QualifiedName}" />
<DataGridTextColumn Header="{Resx ResxName=Rubberduck.UI.RubberduckUI, Key=CodeInspectionResults_Issue}" Binding="{Binding Description}" />
</DataGrid.Columns>
</controls:GroupingGrid>
<resx:BusyIndicator Grid.Row="1" Width="36" Height="36" Visibility="{Binding IsBusy, Converter={StaticResource BoolToVisibility}}" />
<GridSplitter Grid.Row="2" Height="5" ShowsPreview="True" Cursor="SizeNS" HorizontalAlignment="Stretch"/>
<Border Grid.Row="3" BorderThickness="0,1,0,0" BorderBrush="DimGray">
<StackPanel Orientation="Vertical" MinHeight="48" Background="Gainsboro">
<StackPanel Margin="4" Orientation="Horizontal" HorizontalAlignment="Stretch">
<Image Style="{StaticResource IconStyle}" VerticalAlignment="Center"
Source="{Binding SelectedItem.Inspection.Severity, Converter={StaticResource SeverityIconConverter}}"/>
<TextBlock Margin="4" Text="{Binding SelectedItem.Inspection.Description}" FontWeight="Bold" TextWrapping="Wrap"/>
</StackPanel>
<TextBlock Margin="4" Text="{Binding SelectedItem.Inspection.Meta}" FontSize="10" TextWrapping="Wrap"/>
<TextBlock Margin="8" Text="{Binding SelectedItem.QualifiedSelection}" FontSize="10" TextWrapping="Wrap" />
<WrapPanel>
<Button Style="{StaticResource LinkButton}" Margin="4"
Visibility="{Binding CanExecuteQuickFixInModule, Converter={StaticResource BoolToVisibility}}"
Command="{Binding QuickFixInModuleCommand}"
Content="{Resx ResxName=Rubberduck.UI.RubberduckUI, Key=QuickFix_ThisModule}" />
<Button Style="{StaticResource LinkButton}" Margin="4"
Visibility="{Binding CanExecuteQuickFixInProject, Converter={StaticResource BoolToVisibility}}"
Command="{Binding QuickFixInProjectCommand}"
Content="{Resx ResxName=Rubberduck.UI.RubberduckUI, Key=QuickFix_ThisProject}" />
<Button Style="{StaticResource LinkButton}" Margin="4"
Visibility="{Binding CanDisableInspection, Converter={StaticResource BoolToVisibility}}"
Command="{Binding DisableInspectionCommand}"
Content="{Resx ResxName=Rubberduck.UI.RubberduckUI, Key=DisableThisInspection}" />
</WrapPanel>
</StackPanel>
</Border>
</Grid>
I'd like to bring your attention to this part:
<controls:GroupingGrid Grid.Row="1" x:Name="ResultsByModuleGrid" ShowGroupingItemCount="True" SelectedItem="{Binding SelectedItem}" ItemsSource="{Binding Source={StaticResource ResultsByModule}}" Visibility="{Binding IsChecked, ElementName=GroupByModule, Converter={StaticResource BoolToVisibility}}">
And:
<controls:GroupingGrid Grid.Row="1" x:Name="ResultsByInspectionTypeGrid" ShowGroupingItemCount="True" SelectedItem="{Binding SelectedItem}" ItemsSource="{Binding Source={StaticResource ResultsByInspectionType}}" Visibility="{Binding IsChecked, ElementName=GroupByInspectionType, Converter={StaticResource BoolToVisibility}}">
It completely reeks of a hack, but I haven't managed to get anything else to work. Is there any better way to do this? Copy-Pasta can't be the right way... right? ...Anything else smells in this markup?
I can provide the C# ViewModel and related classes if additional context is needed, but I wanted this post to be about the markup.
-
2\$\begingroup\$ FWIW, for somebody who doesn't know wpf, this looks perfectly reasonable, nice code. I'm more concerned that further optimization attempts might be venturing into over-engineering-land, and risk increasing complexity rather than reducing it \$\endgroup\$janos– janos2016年02月16日 07:23:08 +00:00Commented Feb 16, 2016 at 7:23
1 Answer 1
Apparently toggling visibility of controls like that, is common practice in WPF; there's no way to toggle the ItemsSource
in xaml, so what you have is pretty much as good as it gets.
Minor things stick out though:
xmlns:resx="clr-namespace:Rubberduck.UI"
The only thing in that namespace is the BusyIndicator
control, which would be better off in the controls
namespace.
Probably an oversight, but every label is localized, except the ones in the "Grouping" menu, which are hard-coded:
<MenuItem Header="Grouping">
The x:Name
attributes on the GroupingGrid
controls shouldn't be needed (a quick search in the Rubberduck repository shows it's not referenced anywhere), get rid of them.