I have a Control defined by a ControlTemplate shown below:
<ControlTemplate x:Key="UXIconComboBoxControlTemplate" TargetType="{x:Type controls:UXIconComboBox}" >
<ControlTemplate.Resources>
<converters:IconComboBoxDropdownHorizontalOffsetMultiConverter x:Key="ComboBoxDropdownHorizontalOffsetConverter"/>
</ControlTemplate.Resources>
<Grid
x:Name="rootGrid"
Width="{TemplateBinding HitAreaWidth}"
Height="{TemplateBinding HitAreaHeight}"
Background="{TemplateBinding Background}"
>
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<controls:ExtendedHitAreaButton
Grid.Row="0"
x:Name="PopupExpanderButton" << Popup Button
IsTabStop="True"
AutomationProperties.AutomationId="UXIconComboBox"
Style="{StaticResource PopupExpanderButtonStyle}">
</controls:ExtendedHitAreaButton>
<Popup
x:Name="IconComboBoxPopup"
Grid.Row="1"
AutomationProperties.AutomationId="UXIconComboBoxPopup"
Placement="Bottom"
PlacementTarget="{Binding ElementName=PopupExpanderButton}"
MinWidth="200"
StaysOpen="False">
<Popup.HorizontalOffset>
<MultiBinding Converter="{StaticResource ComboBoxDropdownHorizontalOffsetConverter}">
<Binding ElementName="PopupExpanderButton" Path="ActualWidth"/>
<Binding ElementName="IconComboBoxPopup_ListBox" Path="ActualWidth"/>
</MultiBinding>
</Popup.HorizontalOffset>
<ListBox
x:Name="IconComboBoxPopup_ListBox"
AutomationProperties.AutomationId="UXIconComboBoxListBox"
ItemsSource="{Binding ItemsSource, RelativeSource={RelativeSource TemplatedParent}}"
IsSynchronizedWithCurrentItem="True"
Focusable="True"
KeyboardNavigation.TabNavigation="Cycle"
KeyboardNavigation.DirectionalNavigation="Cycle"
BorderThickness="0"
ItemContainerStyle="{StaticResource ListBoxContainerStyle}"
ItemTemplate="{Binding ItemTemplate, RelativeSource={RelativeSource TemplatedParent}}">
</ListBox>
<VisualStateManager.VisualStateGroups>
<VisualStateGroup x:Name="PopupStates">
<VisualState x:Name="PopupClosed">
<Storyboard>
<BooleanAnimationUsingKeyFrames Storyboard.TargetName="IconComboBoxPopup"
Storyboard.TargetProperty="IsOpen">
<DiscreteBooleanKeyFrame KeyTime="0:0:0.25"
Value="False" />
</BooleanAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames
Storyboard.TargetName="rootGrid"
Storyboard.TargetProperty="(Panel.Background)">
<DiscreteObjectKeyFrame KeyTime="0:0:0.25" Value="{DynamicResource BrushIconComboBox_Popup_Closed_Background}" />
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</VisualState>
<VisualState x:Name="PopupOpen">
<Storyboard>
<BooleanAnimationUsingKeyFrames Storyboard.TargetName="IconComboBoxPopup"
Storyboard.TargetProperty="IsOpen">
<DiscreteBooleanKeyFrame KeyTime="0:0:0.25"
Value="True" />
</BooleanAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames
Storyboard.TargetName="rootGrid"
Storyboard.TargetProperty="(Panel.Background)">
<DiscreteObjectKeyFrame KeyTime="0:0:0.25" Value="{DynamicResource BrushIconComboBox_Popup_Open_Background}" />
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</VisualState>
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
</Popup>
</Grid>
</ControlTemplate>
I also have a Click event handler for the PopupExpanderButton as follows:
/// <summary>
/// Handler for the ExtendedHitAreaButton click event
/// </summary>
/// <param name="sender">listBoxItem info</param>
/// <param name="e">routed event info</param>
public void PopupExpanderButton_Click(object sender, RoutedEventArgs e)
{
Debug.WriteLine($"PopupExpanderButton_Click called - popup state {IconComboBoxPopup.IsOpen}");
var popupState = IconComboBoxPopup.IsOpen ? PopupClosed : PopupOpen;
var result = VisualStateManager.GoToState(this, popupState, true);
e.Handled = true;
}
If I click on the PopupExpanderButton, the Click event above is executed and the Popup window is displayed. NOTE that the PopupExpanderButton is simply a subclass of Button that includes a HitTestCore function that calls PointHitTestResult.
Without moving the mouse away from over the PopupExpanderButton, if I click on the PopupExpanderButton again, the popup window gets a lost-focus event and closes as it should, but then the Click event above is executed, IconComboBoxPopup.IsOpen is false, the GoToState is executed with a PopupOpen value .... but the Popup window does not open. The result value of the GoToState call is always True. Changing the method to GoToElementState makes no difference.
If I keep clicking on the PopupExpanderButton, the Click event above is executed with a PopupOpen value, but the Popup window will not open again.
If I click away from the PopupExpanderButton to some other part of the dialog, and then go back and click the PopupExpanderButton, the Popup window WILL appear!!
Can anyone tell me why the GoToState method is not working after the first Click event?
Thanks in advance!!
It's recommended to attach the
VisualStateManager.VisualStateGroupsattached property to the root element of theControlTemplate, which is theGridin your case. It's currently attached to thePopup.It's also recommended that you don't set
RoutedEventArgs.Handledtotrue. This would stop theRoutedEventfrom traversing the element tree, which could introduce unexpected behavior.Usually client code of your control would expect such events to traverse. In your case a reason to stop this event would be if you want to convert the
Button.Clickevent to a more meaningfulIconComboBoxPopupOpenedevent. In this case you would first handle theButton.Click, mark it as handled to stop it and raise theIconComboBoxPopupOpenedinstead.The problem you are experienceing is becuase your visual states are not transitioned correctly. You forgot to transition the state from
"PopupOpen"back to"PopupClosed".Currently, although the
Popupis closed the control's state is still"PopipOpen"- that's why when trying to transition the state to"PopupOpen"does nothing, butVisualStateManager.GoToStatereturnstrue.The return value is always
truewhen the transition was successful or the control is already in the desired state.Because you allow the
Popupto close itself implicitly by losing focus, you must track thePopup.IsOpenfor this particular case.In fact you must track every close event that is not explicitly triggered by the hosting control (e.g. by handling key gestures) and handle it by transitioning the visual state to
"PopupClosed".Same applies for open events in case the
Popupwas opened by an unknown actor.MyCustomControl.cs