Category Archives: WPF

How to get a ListView with expandable controls to resize

I created a ListView and used an expandable control for each of the ListView’s items. Everything was fine until I noticed that after collapsing an item the ListView did not resize. Resizing worked fine in the other direction, i.e. the Listview would grow in size when an item was expanded. I thought that I was going to have to do all sorts of jiggery pokery with MeasureOverride or the such like but actually the solution was really simple.

Just set the ItemsPanel to a type that automatically resizes:

<ListView.ItemsPanel>
  <ItemsPanelTemplate>
    <StackPanel/>
  </ItemsPanelTemplate>
</ListView.ItemsPanel>

So here is my ListView in its entirety:

<ListView x:Name="lvMenus" AllowDrop="True" ItemContainerStyle="{DynamicResource ItemContStyle}"
    		HorizontalAlignment="Stretch">
	<ListView.ItemsPanel>
		<ItemsPanelTemplate>
			<StackPanel/>
		</ItemsPanelTemplate>
	</ListView.ItemsPanel>
	<ListView.View>
		<GridView>
			<GridViewColumn Header="Titles">
				<GridViewColumn.CellTemplate>
					<DataTemplate>
						<spEtr:Inspector
					          AssetId="{Binding Id, Mode=OneWay}"
						  LongTitle="{Binding LongTitle, Mode=TwoWay}"
						  LongTitleChanged="Inspector_LongTitleChanged"
						  LongTitleLostFocus="Inspector_LongTitleLostFocus"
						  LongTitleGotFocus="Inspector_LongTitleGotFocus"
						  ShortTitle="{Binding ShortenedTitle, Mode=TwoWay}"
						  ShortTitleChanged="Inspector_ShortTitleChanged"
						  ShortTitleFocused="Inspector_ShortTitleFocused"
						  ShortTitleLostFocus="Inspector_ShortTitleLostFocus"
						  ExpanderClicked="Inspector_ExpanderClicked"
						  LongTitleErrorStatusChanged="Inspector_LongTitleErrorStatusChanged"
							/>
					</DataTemplate>
				</GridViewColumn.CellTemplate>
			</GridViewColumn>
		</GridView>
	</ListView.View>
</ListView>
Advertisements

WPF Using a trigger when Validation has error

There is much documentation out there regarding how to use ValidationRules together with an ErrorTemplate in order to highlight invalid user data. However, I found it a little trickier to find out how to trigger some other kind of behaviour.

Here is my scenario:
I have two text boxes, the second of which is collapsed unless a validation error occurs in the first text box. This seems like pretty much a run of the mill scenario but it was a little trickier to get working than simply putting a red border round the first text box.

I put all the validation and triggers in a Style.  Here is the XAML describing the two text boxes:

<StackPanel Name="ExpanderStackPnl" MinWidth="440">
  <Expander x:Name="TitleExpander"
                      Header="{Binding ElementName=InspectorRoot, Path=LongTitle}"
              Collapsed="TitleExpander_Collapsed" Expanded="TitleExpander_Expanded">
    <StackPanel Margin="5,5,5,5" x:Name="StackInsideExpander">
		   <spCntrls:SportTitle x:Name="spTitle"
                             SpLabel="Desired title (fits on most platforms):"
                             Style="{StaticResource LongTitleStyle}"
                             TitleChanged="LongTitle_Changed"
                             LostFocus="LongTitle_LostFocus"
                             ErrorStatusChanged="LongTitle_ErrorStatusChanged"
                              GotFocus="LongTitle_GotFocus">

          <spCntrls:SportTitle.UserText>
            <Binding Path="LongTitle"
                              ElementName="InspectorRoot"
                              UpdateSourceTrigger="PropertyChanged"
                              NotifyOnValidationError="True"
                             Mode="TwoWay">
              <Binding.ValidationRules>
                <rules:FitsAllValidationRule/>
              </Binding.ValidationRules>
            </Binding>
          </spCntrls:SportTitle.UserText>
        </spCntrls:SportTitle>

        <spCntrls:SportTitle x:Name="spShortTitle"
                             SpLabel="Abbreviated Title (must fit ALL platforms)"
                             Style="{StaticResource ShortTitleStyle}"
                             TitleChanged="ShortTitle_Changed"
                             GotFocus="ShortTitle_GotFocus"
                             LostFocus="ShortTitle_LostFocus">
          <spCntrls:SportTitle.UserText>
            <Binding Path="ShortTitle"
                              ElementName="InspectorRoot"
                              UpdateSourceTrigger="PropertyChanged"
                              NotifyOnValidationError="True"
                             Mode="TwoWay">
              <Binding.ValidationRules>
                <rules:FitsAllValidationRule/>
              </Binding.ValidationRules>
            </Binding>
          </spCntrls:SportTitle.UserText>
        </spCntrls:SportTitle>
     </StackPanel>
   </Expander>
</StackPanel>

As you can see, I have attached a style to each of them and that is where the behaviour is described.  I used a GlobalDictionary to store the styles creating a namespace to refer to the class (named Inspector) where my text boxes reside.

<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
                    xmlns:edtr="clr-namespace:BBCNewsI.Cps2Dtext.SportEditor"
    xmlns:spCntrls="clr-namespace:BBCNewsI.Cps2Dtext.SportEditor.Controls">

The XAML which is of interest is in the style named ShortTitleStyle. Using a DataTrigger you can trigger on changes to another control:

<DataTrigger Binding="{Binding Path=(Validation.HasError), ElementName=spTitle}" Value="true">
	<Setter Property="Visibility" Value="Visible"/>
</DataTrigger>
<DataTrigger Binding="{Binding Path=(Validation.HasError), ElementName=spTitle}" Value="false">
	<Setter Property="Visibility" Value="Collapsed"/>
</DataTrigger>

The thing that I found interesting is that a sibling control is simply referred to by its name, there is no need for any fancy namespace references or Ancestor like binding.

You might have noticed that these controls are inside an expander. This creates in interesting problem when there is an error and the expander is collapsed, namely that you can still see the border of the error template! To get round this, I used another DataTrigger with a dependency property which indicates the expanded state of the expander to set the error template to null:

<DataTrigger Binding="{Binding Path=TitleExpanded,
        RelativeSource={RelativeSource AncestorType={x:Type spCntrls:Inspector}}}"
                   Value="False">
  <Setter Property="Validation.ErrorTemplate" Value="{x:Null}"/>
 </DataTrigger>

<DataTrigger Binding="{Binding Path=TitleExpanded,
        RelativeSource={RelativeSource AncestorType={x:Type spCntrls:Inspector}}}"
                   Value="True">
   <Setter Property="spCntrls:SportTitle.ToolTip"
                Value="Enter title for scene, both text fields usually have the same text " +
                      "unless the desired title is too long to fit on all platforms"/>
</DataTrigger>

Here are both styles that have been applied to the text boxes in their entirety:

<Style x:Key="LongTitleStyle" TargetType="{x:Type spCntrls:SportTitle}">
    <Setter Property="Validation.ErrorTemplate">
      <Setter.Value>
        <ControlTemplate x:Name="InspectorErrorTemplate">
          <StackPanel Orientation="Vertical">
            <Border BorderBrush="Goldenrod" BorderThickness="1">
              <AdornedElementPlaceholder Name="adornerPlaceholder"/>
            </Border>
          </StackPanel>
        </ControlTemplate>
      </Setter.Value>
    </Setter>
    <Style.Triggers>
      <DataTrigger Binding="{Binding Path=TitleExpanded,
        RelativeSource={RelativeSource AncestorType={x:Type spCntrls:Inspector}}}"
                   Value="False">
        <Setter Property="Validation.ErrorTemplate" Value="{x:Null}"/>
      </DataTrigger>

      <DataTrigger Binding="{Binding Path=TitleExpanded,
        RelativeSource={RelativeSource AncestorType={x:Type spCntrls:Inspector}}}"
                   Value="True">
        <Setter Property="spCntrls:SportTitle.ToolTip"
                Value="Enter title for scene, both text fields usually have the same " +
			"text unless the desired title is too long to fit on all platforms"/>
     </DataTrigger>

      <Trigger Property="Validation.HasError" Value="true">
        <Setter Property="spCntrls:SportTitle.UserTextForeground" Value="Maroon"/>
        <Setter Property="spCntrls:SportTitle.HasError" Value="true"/>
        <Setter Property="ToolTip" Value="{Binding RelativeSource={RelativeSource Self},
          Path=(Validation.Errors)[0].ErrorContent}"/>
      </Trigger>
      <Trigger Property="Validation.HasError" Value="false">
        <Setter Property="spCntrls:SportTitle.HasError" Value="false"/>
      </Trigger>
    </Style.Triggers>
  </Style>

  <!-- Style for short version of the title -->
  <Style x:Key="ShortTitleStyle" BasedOn="{StaticResource LongTitleStyle}"
							TargetType="{x:Type spCntrls:SportTitle}">
    <Setter Property="Validation.ErrorTemplate">
      <Setter.Value>
        <ControlTemplate x:Name="InspectorErrorTemplate">
          <StackPanel Orientation="Vertical">
            <Border BorderBrush="Red" BorderThickness="1">
              <AdornedElementPlaceholder Name="adornerPlaceholder"/>
            </Border>
          </StackPanel>
        </ControlTemplate>
      </Setter.Value>
    </Setter>
    <Style.Triggers>
      <Trigger Property="Validation.HasError" Value="true">
        <Setter Property="spCntrls:SportTitle.UserTextForeground" Value="Red"/>
        <Setter Property="ToolTip" Value="Alternative title is not short enough"/>
      </Trigger>

      <DataTrigger Binding="{Binding Path=(Validation.HasError), ElementName=spTitle}" 
               Value="true">
        <Setter Property="Visibility" Value="Visible"/>
      </DataTrigger>
      <DataTrigger Binding="{Binding Path=(Validation.HasError), ElementName=spTitle}" 
               Value="false">
        <Setter Property="Visibility" Value="Collapsed"/>
      </DataTrigger>
    </Style.Triggers>
  </Style>