Popup open triggered by DataTrigger - c#

I am trying to open popup according to data change in my ViewModel:
Popup is defined as this:
<Popup x:Name="popup"
AllowsTransparency="True"
Focusable="False"
IsHitTestVisible="False"
Placement="Bottom"
PopupAnimation="Slide"
StaysOpen="False">... </Popup>
And there I have user control
<UserControl>
...
<ControlTemplate>
<ControlTemplate.Triggers>
<DataTrigger Binding="{Binding PopupOpened}" Value="True">
<DataTrigger.EnterActions>
<BeginStoryboard Storyboard="{StaticResource ShowPopup}"/>
</DataTrigger.EnterActions>
</DataTrigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</UserControl>
And my animation is defined as (in resources):
<Storyboard x:Key="ShowPopup">
<BooleanAnimationUsingKeyFrames Storyboard.TargetName="popup" Storyboard.TargetProperty="(Popup.IsOpen)">
<DiscreteBooleanKeyFrame KeyTime="00:00:00.00" Value="True" />
</BooleanAnimationUsingKeyFrames>
</Storyboard>
If I trigger this animation from EventTrigger on some control's triggers (button for example) it works correctly.
It doesn't work correctly inside DataTrigger.
EDIT:
I found an issue:
If you are controlling Popup.IsOpen property with Animation + binding, Binding to that property will work only till you will use animation to change that property. After that Binding will not work anymore. Therefore you have to always change IsOpen property through Animation or Binding not mixing!

There are several errors in your example which obscure the real issue.
Your DataTrigger element is not closed and 'StaticResources' should be StaticResource.
But ultimately, template for UserControl is not used right, you missed UserControl.Template setting. If we fix it, we'll see the exception:
<UserControl>
<UserControl.Template>
<ControlTemplate>
<ControlTemplate.Triggers>
<DataTrigger Binding="{Binding PopupOpened}" Value="True">
<DataTrigger.EnterActions>
<BeginStoryboard Storyboard="{StaticResource ShowPopup}" />
</DataTrigger.EnterActions>
</DataTrigger>
</ControlTemplate.Triggers>
<!-- Some content for template to have substance -->
<TextBlock />
</ControlTemplate>
</UserControl.Template>
</UserControl>
Here's the result:
'popup' name cannot be found in the name scope of
'System.Windows.Controls.ControlTemplate'.
The easiest way to actually do that, IMO, is a simple direct binding:
<Popup x:Name="popup"
IsOpen="{Binding PopupOpened, Mode=TwoWay}">
Or, if you really need the trigger, you can move it to Popup itself:
<Popup x:Name="popup">
<Popup.Style>
<Style>
<Style.Triggers>
<DataTrigger Binding="{Binding PopupOpened}" Value="True">
<DataTrigger.EnterActions>
<BeginStoryboard>
<Storyboard>
<BooleanAnimationUsingKeyFrames Storyboard.TargetProperty="IsOpen">
<DiscreteBooleanKeyFrame KeyTime="00:00:00.00" Value="True" />
</BooleanAnimationUsingKeyFrames>
</Storyboard>
</BeginStoryboard>
</DataTrigger.EnterActions>
</DataTrigger>
</Style.Triggers>
</Style>
</Popup.Style>
<TextBlock />
</Popup>

Related

Selecting ListBox items is not firing SelectionChanged event [duplicate]

I have added a DataTemplate to a ListBox class to bind my collection to:
<ListBox x:Name="lstEmails" Height="259" Margin="12,0,12,41" Width="276"
SelectionChanged="lstEmails_SelectionChanged">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<Label Visibility="Hidden" Content="{Binding ID}"></Label>
<TextBox Width="200" Text="{Binding EmailAddress}"></TextBox>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
This does exactly what I want it to do. Although when I click on the TextBox, the ListBox does not automatically set the associated ListItem as Selected. I could do this in code, but I would prefer to use this as a component (no surprises there then).
Any ideas on how to achieve this?
That doesn't seem to work, it won't let me click on anything. Have I missed something. Here is my new XAML.
<UserControl.Resources>
<!--<TextBox x:Key="TB" x:Name="TextBoxInsideListBoxItemTemplate">
<TextBox.Style>-->
<Style TargetType="{x:Type TextBox}">
<Setter Property="IsHitTestVisible" Value="False" />
<Style.Triggers>
<DataTrigger Binding="{Binding IsSelected, RelativeSource={RelativeSource FindAncestor,AncestorType={x:Type ListBoxItem}, AncestorLevel=1}}"
Value="True">
<Setter Property="IsHitTestVisible" Value="True" />
</DataTrigger>
</Style.Triggers>
</Style>
<!--</TextBox.Style>
</TextBox>-->
</UserControl.Resources>
<Grid>
<ListBox x:Name="lstEmails" Height="259" Margin="12,0,12,41" Width="276" SelectionChanged="lstEmails_SelectionChanged">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<!--<Label Visibility="Hidden" Content="{Binding ID}"></Label>-->
<TextBox Width="220" Text="{Binding EmailAddress}" >
</TextBox>
<!--<TextBox Width="220" Text="{Binding EmailAddress}" GotFocus="TextBox_GotFocus"></TextBox>-->
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
<Button Width="20" Margin="12,0,0,12" Name="btnAdd" VerticalAlignment="Bottom" Click="btnAdd_Click" Height="23" HorizontalAlignment="Left">+</Button>
<Button Width="20" HorizontalAlignment="Left" Margin="30,0,0,12" Name="btnRemove" VerticalAlignment="Bottom" Click="btnRemove_Click" Height="23">-</Button>
<Button Height="23" HorizontalAlignment="Right" Margin="0,0,12,12" Name="btnApply" VerticalAlignment="Bottom" Width="49" Click="btnApply_Click">Apply</Button>
</Grid>
I think the click twice bit is good functionality.
You can trigger on the property IsKeyboardFocusWithin in the ItemContainerStyle and set IsSelected to true.
<ListBox.ItemContainerStyle>
<Style TargetType="{x:Type ListBoxItem}">
<Style.Triggers>
<DataTrigger Binding="{Binding IsKeyboardFocusWithin, RelativeSource={RelativeSource Self}}" Value="True">
<DataTrigger.EnterActions>
<BeginStoryboard>
<Storyboard>
<BooleanAnimationUsingKeyFrames Storyboard.TargetProperty="(ListBoxItem.IsSelected)">
<DiscreteBooleanKeyFrame KeyTime="0" Value="True"/>
</BooleanAnimationUsingKeyFrames>
</Storyboard>
</BeginStoryboard>
</DataTrigger.EnterActions>
</DataTrigger>
</Style.Triggers>
</Style>
</ListBox.ItemContainerStyle>
You could also use a Setter instead of a single frame animation but then the selection will be lost again once the focus leaves the ListBox:
<ListBox.ItemContainerStyle>
<Style TargetType="{x:Type ListBoxItem}">
<Style.Triggers>
<DataTrigger Binding="{Binding IsKeyboardFocusWithin, RelativeSource={RelativeSource Self}}" Value="True">
<Setter Property="IsSelected" Value="True"/>
</DataTrigger>
</Style.Triggers>
</Style>
</ListBox.ItemContainerStyle>
If you have multiple instance of ListBox then you may consider using your custom listbox (by deriving it from ListBox). See the explanation here.
Or, use this hack if you have only 1 (or only small number of) such ListBox and don't want to create a separate class for that:
<TextBox x:Name="TextBoxInsideListBoxItemTemplate" ... >
<TextBox.Style>
<Style TargetType="{x:Type TextBox}">
<Setter Property="IsHitTestVisible" Value="False" />
<Style.Triggers>
<DataTrigger
Binding="{Binding IsSelected, RelativeSource={RelativeSource FindAncestor,
AncestorType={x:Type ListBoxItem}, AncestorLevel=1}}"
Value="True">
<Setter Property="IsHitTestVisible" Value="True" />
</DataTrigger>
</Style.Triggers>
</Style>
</TextBox.Style>
</TextBox>
Note that you'll have to click once again to edit text in the TextBox (which is actually cool according to me).
I had a situation where the selection of a listbox item would change its layout, so the control might have moved away from the cursor before the mouse button is released. I have found no better solution than to use a slight delay in the Storyboard if I want to keep everything in xaml.
More importantly, GotKeyboardFocus seems to work better than IsKeyboardFocusWithin for repeated selections.
<EventTrigger RoutedEvent="GotKeyboardFocus">
<BeginStoryboard>
<Storyboard>
<BooleanAnimationUsingKeyFrames Storyboard.TargetProperty="IsSelected">
<DiscreteBooleanKeyFrame KeyTime="00:00:00.3" Value="True"/>
</BooleanAnimationUsingKeyFrames>
</Storyboard>
</BeginStoryboard>
</EventTrigger>

Storyboard label style how to apply background when datatrigger false?

I have a simple for with a set of labels on a grid, i have added a label style storyboard for flashing when NodeFlash is set to true and this works fine, however when NodeFlash set to false the label remains white background / white text and not the background color set in NodeStatus ( ie green, red etc ). where would i need to force the background color setting in the exit actions or else where?
heres the view.xaml
<Grid>
<ItemsControl ItemsSource = "{Binding Path = CIs}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Grid>
<Label
Content = "{Binding Path = NodeName, Mode = OneWay}"
Background = "{Binding Path = NodeStatus, Mode = OneWay}"
Tag="{Binding Path = Nodeid, Mode = OneWay}"
Foreground="White"
FontFamily="Arial Black"
HorizontalContentAlignment="Center"
BorderBrush="Black"
BorderThickness="1,1,1,1">
<Label.Style>
<Style TargetType="{x:Type Label}">
<Style.Resources>
<Storyboard x:Key="flashAnimation" >
<DoubleAnimation Storyboard.TargetProperty="Opacity" From="1" To="0" AutoReverse="True" Duration="0:0:0.5" RepeatBehavior="Forever" />
</Storyboard>
</Style.Resources>
<Setter Property="Visibility" Value="Hidden" />
<Style.Triggers>
<DataTrigger Binding="{Binding NodeFlash}" Value="True">
<Setter Property="Visibility" Value="Visible" />
<DataTrigger.EnterActions>
<BeginStoryboard Name="flash" Storyboard="{StaticResource flashAnimation}" />
</DataTrigger.EnterActions>
<DataTrigger.ExitActions>
<StopStoryboard BeginStoryboardName="flash"/>
</DataTrigger.ExitActions>
</DataTrigger>
</Style.Triggers>
</Style>
</Label.Style>
</Label>
</Grid>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Grid>
Thanks again
found it i think? i need to change this:
<Setter Property="Visibility" Value="Hidden" />
to
<Setter Property="Visibility" Value="Visible" />
think it was hiding all non styled labels?
Thanks

Using a Storyboard on a ToolTip WPF

I am trying to create a storyboard, so when a user clicks on a textbox, its copies the text to their clipboard and shows a tooltip saying copied, which then fades away.
Here is my attempt:
xaml:
<TextBox Name="PolyValue" Text="{Binding .}" IsReadOnly="True" BorderThickness="0" Background="White"
VerticalAlignment="Center" PreviewMouseDown="CopyTextBox" >
<TextBox.ToolTip>
<ToolTip Style="{StaticResource TooltipPopupFadeAway}" IsOpen="True" Opacity="0" Background="Transparent" BorderThickness="0">
<Border Background="White" BorderBrush="Black" BorderThickness="1" CornerRadius="3" >
<Label Content="Copied" Padding="5, 2" />
</Border>
</ToolTip>
</TextBox.ToolTip>
</TextBox>
Here is the Storyboard:
<Style x:Key="TooltipPopupFadeAway" TargetType="ToolTip">
<Style.Triggers>
<DataTrigger Binding="{Binding Opacity, RelativeSource={RelativeSource Self}}" Value="1">
<DataTrigger.EnterActions>
<BeginStoryboard x:Name="ClosePopupStoryBoard">
<Storyboard>
<DoubleAnimation Storyboard.TargetProperty="Opacity" Duration="00:00:01" From="3" To="0" />
</Storyboard>
</BeginStoryboard>
</DataTrigger.EnterActions>
</DataTrigger>
</Style.Triggers>
</Style>
Code behind:
private void CopyTextBox(object sender, MouseButtonEventArgs e)
{
if(sender is TextBox textBox)
{
Clipboard.SetText(textBox.Text);
(textBox.ToolTip as ToolTip).IsOpen = false;
(textBox.ToolTip as ToolTip).IsOpen = true; //this recalculates the position
(textBox.ToolTip as ToolTip).Opacity = 1;
}
}
This works exactly how I want it to, however it only works once, after it has been shown and the user clicks again nothing happens.
After setting a break point in the CopyTextBox method, the of the ToolTip opacity is 0 even after programmatically setting it to 1.
I am not sure what I am doing wrong?
This is the sort of approach I mean.
Just binding the text property means you need no code.
I'm not sure this does exactly what you want because you seem to have previewmousedown showing the tooltip. Which is a bit odd for a tooltip since mouseover shows them.
<Window.Resources>
<ControlTemplate x:Key="TooltipPopupFadeAway" TargetType="ToolTip">
<Border Background="Yellow">
<TextBlock Text="{Binding PlacementTarget.Text, RelativeSource={RelativeSource AncestorType={x:Type ToolTip}}}"
Name="TheText"
/>
</Border>
<ControlTemplate.Triggers>
<EventTrigger RoutedEvent="ToolTip.Opened">
<BeginStoryboard>
<Storyboard TargetProperty="Opacity">
<DoubleAnimation From="1.0" To="0" Duration="0:0:2" />
</Storyboard>
</BeginStoryboard>
</EventTrigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Window.Resources>
<Grid>
<TextBox>
<TextBox.ToolTip>
<ToolTip Template="{StaticResource TooltipPopupFadeAway}"/>
</TextBox.ToolTip>
</TextBox>
</Grid>
And you could set that template and tooltip etc via style if it suited you better. I used a yellow background so I can see it easy. The tooltip probably doesn't exactly match what you had.
You should trigger directly on the IsOpen property. Make sure to set the default Opacity to 0, and do not explicitly set it to 1 afterwards.
<Style x:Key="TooltipPopupFadeAway" TargetType="ToolTip">
<Setter Property="Background" Value="Transparent"/>
<Setter Property="BorderThickness" Value="0"/>
<Setter Property="Opacity" Value="0"/>
<Setter Property="IsOpen" Value="False"/>
<Style.Triggers>
<Trigger Property="IsOpen" Value="True">
<Trigger.EnterActions>
<BeginStoryboard>
<Storyboard>
<DoubleAnimation Storyboard.TargetProperty="Opacity"
Duration="0:0:1" From="3" To="0"/>
</Storyboard>
</BeginStoryboard>
</Trigger.EnterActions>
</Trigger>
</Style.Triggers>
</Style>
In code behind, do not to set the Opacity:
private void CopyTextBox(object sender, MouseButtonEventArgs e)
{
if (sender is TextBox textBox)
{
Clipboard.SetText(textBox.Text);
((ToolTip)textBox.ToolTip).IsOpen = false;
((ToolTip)textBox.ToolTip).IsOpen = true;
}
}

Stop wpf Storyboard and hold actual value

I have following wpf code
<Window.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary>
<Image x:Key="MyImage" Source="../Assets/icon.ico"/>
</ResourceDictionary>
</ResourceDictionary.MergedDictionaries>
<Storyboard x:Key="AkomiLogoAnimation">
<DoubleAnimation
Storyboard.TargetProperty="(Image.RenderTransform).(RotateTransform.Angle)"
By="360"
SpeedRatio="0.5"
RepeatBehavior="Forever"
FillBehavior="Stop"
/>
</Storyboard>
</ResourceDictionary>
</Window.Resources>
<TextBox x:Name="txtAuto"/>
<Image Source="../Assets/icon.ico" Width="20" Height="20" RenderTransformOrigin=".5,.5">
<Image.Resources>
<converter:IsNullConverter x:Key="IsNullConverter"/>
</Image.Resources>
<Image.RenderTransform>
<RotateTransform Angle="0" />
</Image.RenderTransform>
<Image.Style>
<Style>
<Style.Triggers>
<DataTrigger Binding="{Binding ElementName=txtAuto, Path=Text, Converter={StaticResource IsNullConverter}, NotifyOnSourceUpdated=True, UpdateSourceTrigger=PropertyChanged}" Value="False">
<DataTrigger.EnterActions>
<BeginStoryboard Name="AkomiRotation" Storyboard="{StaticResource AkomiLogoAnimation}">
</BeginStoryboard>
</DataTrigger.EnterActions>
</DataTrigger>
<DataTrigger Binding="{Binding ElementName=txtAuto, Path=Text, Converter={StaticResource IsNullConverter}, NotifyOnSourceUpdated=True, UpdateSourceTrigger=PropertyChanged}" Value="True">
<DataTrigger.EnterActions>
<StopStoryboard BeginStoryboardName="AkomiRotation" >
</StopStoryboard>
</DataTrigger.EnterActions>
</DataTrigger>
</Style.Triggers>
</Style>
</Image.Style>
</Image>
The goal is to rotate the image as soon as some text has been entered. But stopping the storyboard lets the image angle jump into it's initial value.
I tried to add FillBehavior="HoldEnd" to the Storyboard definition, but that doesn't work.
Any suggestions? Maybe I can access the angle-value at the end of the animation anyhow and set this value in RotateTransform
Have you tried PauseStoryboard, instead of stop? Also, you should change fillbehavior on the animation

How to make this style generic?

I have the following DataGridCell style:
<Style TargetType="DataGridCell" x:Key="DateChangeAnimation" >
<Style.Triggers>
<MultiDataTrigger>
<MultiDataTrigger.Conditions>
<Condition Binding="{Binding Path=Date.ValueChanged}" Value="True" />
<Condition Binding="{Binding Source={StaticResource BindProxy}, Path=Data.Columns.Date.NotifyChange}" Value="True" />
</MultiDataTrigger.Conditions>
<MultiDataTrigger.EnterActions>
<BeginStoryboard>
<Storyboard>
<ColorAnimation AutoReverse="True" From="#1F1F1F" To="#FFFF88" Duration="0:0:0.2" Storyboard.TargetProperty="Background.Color" FillBehavior="Stop" />
</Storyboard>
</BeginStoryboard>
</MultiDataTrigger.EnterActions>
</MultiDataTrigger>
</Style.Triggers>
</Style>
This style has the bindings set for one column cell, as I need to apply it to 20 or more columns, is there a way to define the style once in a generic manner and to instantiate it as many times as needed specifying only the variables to bind?
I'm not completely sure what you're asking, but if it's what I think it is then the short answer is No (maybe Yes).
I'm saying this because I think that you're looking to be able to somehow replace the BindingExpressions you wrote without a new style being created upon each variation.
So, why maybe Yes? Well, you could use a DynamicResource and programmatically create the styles, replacing the BindingExpressions where necessary.
As another alternative, you could create a custom control (or UserControl) that exposes dependency properties and then bind your variation of values to those properties, but that might not be worth the trouble.
Here's what I'm guessing you're trying to accomplish by using a simpler example.
<Style x:Key="MyDesiredGenericStyle" x:TargetType="TextBox">
<Setter Property="Template">
<Setter.Value>
<Border ...>
<StackPanel>
<TextBlock Text="{Binding SomeGenericWay}" />
<ContentPresenter />
</StackPanel>
</Border>
</Setter.Value>
</Setter>
</Style>
vs.
<Style x:Key="MyDesiredStyleA" x:TargetType="TextBox">
<Setter Property="Template">
<Setter.Value>
<Border ...>
<StackPanel>
<TextBlock Text="{Binding Path=PropA}" />
<ContentPresenter />
</StackPanel>
</Border>
</Setter.Value>
</Setter>
</Style>
<Style x:Key="MyDesiredStyleB" x:TargetType="TextBox">
<Setter Property="Template">
<Setter.Value>
<Border ...>
<StackPanel>
<TextBlock Text="{Binding Path=PropB}" />
<ContentPresenter />
</StackPanel>
</Border>
</Setter.Value>
</Setter>
</Style>
Don't give it key so it will be set to all DataGridCell in the application by default.
Simple, remove the key:
<Style TargetType="DataGridCell">
<Style.Triggers>
<MultiDataTrigger>
<MultiDataTrigger.Conditions>
<Condition Binding="{Binding Path=Date.ValueChanged}" Value="True" />
<Condition Binding="{Binding Source={StaticResource BindProxy}, Path=Data.Columns.Date.NotifyChange}" Value="True" />
</MultiDataTrigger.Conditions>
<MultiDataTrigger.EnterActions>
<BeginStoryboard>
<Storyboard>
<ColorAnimation AutoReverse="True" From="#1F1F1F" To="#FFFF88" Duration="0:0:0.2" Storyboard.TargetProperty="Background.Color" FillBehavior="Stop" />
</Storyboard>
</BeginStoryboard>
</MultiDataTrigger.EnterActions>
</MultiDataTrigger>
</Style.Triggers>
</Style>

Categories

Resources