I've been trying to display a message in a control which only appears for a few seconds when I recieve a message saying that an update which I have published has been successfully recieved.
I have a class which implements INotifyPropertyChanged as follows:
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
namespace Wpf.GUI.Views
{
public class AlertBarStatus : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public event RoutedEventHandler VisibleChanged;
private bool _visible ;
private bool _success ;
public AlertBarStatus():this (false,false)
{
}
public AlertBarStatus(bool visible, bool success)
{
_visible = visible;
_success = success;
}
public bool Visible
{
get { return _visible; }
set
{
_visible = value;
OnPropertyChanged("Visible");
if(VisibleChanged != null)
{
VisibleChanged(this, null);
}
}
}
public bool Success
{
get { return _success; }
set
{
_success = value;
OnPropertyChanged("Success");
}
}
protected virtual void OnPropertyChanged(string propertyName)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
}
}
}
I have xaml which contains the control which is as follows:
<UserControl x:Class="Wpf.GUI.AlertBarControl"
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"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300"
DataContext="{Binding RelativeSource={RelativeSource Self}}"
>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="auto"/>
</Grid.RowDefinitions>
<Border x:Name="alertStatusBorder" Height="50" Visibility="Collapsed" Opacity="0" HorizontalAlignment="Stretch" >
<Border.Style>
<Style TargetType="Border">
<Style.Triggers>
<DataTrigger Binding="{Binding AlertBarStatus.Success}" Value="True">
<Setter Property="Background" Value="{StaticResource EnabledBorderBackground}" />
</DataTrigger>
<DataTrigger Binding="{Binding AlertBarStatus.Success}" Value="False">
<Setter Property="Background" Value="{StaticResource DisabledBorderBackground}" />
</DataTrigger>
<DataTrigger x:Name="VisibilityTrigger" Binding="{Binding AlertBarStatus.Visible}" Value="True">
<DataTrigger.EnterActions>
<BeginStoryboard>
<Storyboard x:Name="VisibilityStoryboard">
<ObjectAnimationUsingKeyFrames Duration="0:0:0" Storyboard.TargetProperty="(UIElement.Visibility)">
<DiscreteObjectKeyFrame Value="{x:Static Visibility.Visible}"/>
</ObjectAnimationUsingKeyFrames>
<DoubleAnimation Storyboard.TargetProperty="Opacity"
From="0" To="100" Duration="0:0:5" />
<DoubleAnimation Storyboard.TargetProperty="Opacity"
From="100" To="0" Duration="0:0:5" />
<ObjectAnimationUsingKeyFrames Duration="0:0:5" Storyboard.TargetProperty="(UIElement.Visibility)">
<DiscreteObjectKeyFrame Value="{x:Static Visibility.Collapsed}"/>
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</BeginStoryboard>
</DataTrigger.EnterActions>
<DataTrigger.ExitActions>
<StopStoryboard BeginStoryboardName="VisibilityStoryboard"/>
</DataTrigger.ExitActions>
</DataTrigger>
</Style.Triggers>
</Style>
</Border.Style>
<DockPanel >
<TextBlock DockPanel.Dock="Left" x:Name="alertStatusText" Grid.Column="0" HorizontalAlignment="Left">
<TextBlock.Style>
<Style TargetType="TextBlock">
<Style.Triggers>
<DataTrigger Binding="{Binding AlertBarStatus.Success}" Value="True">
<Setter Property="Text" Value="Successfully published updates" />
</DataTrigger>
<DataTrigger Binding="{Binding AlertBarStatus.Success}" Value="False">
<Setter Property="Text" Value="Failed to publish updates" />
</DataTrigger>
</Style.Triggers>
<Setter Property="FontSize" Value="20"></Setter>
<Setter Property="VerticalAlignment" Value="Center"></Setter>
</Style>
</TextBlock.Style>
</TextBlock>
<!--<Button Height="20" Width="20" Margin="3" DockPanel.Dock="Right" HorizontalAlignment="Right"
Background="Transparent" BorderBrush="Black"
Click="CloseAlertControl_Click">X</Button>-->
</DockPanel>
</Border>
</Grid>
The DataTrigger named VisibilityTrigger does exactly what I want but ONLY when the Success value changes for the first time. It is not then called again.
I know there are several different answers to this question but I have tried all of the solutions I could find but so far none of them work for me.
To clarify, the visibility property can be set to true multiple times before it is set to false, therefore I was thinking that perhaps this could be the issue as I am not setting the value to a different value, but I can see the OnProperty changed method being called so I would not expect that to be an issue.
Any help on this would be much appreciated!
In the end I gave up on attempting to do this in xaml and instead just bound the objects visibility to my underlying object's visibility property as follows.
<Border x:Name="alertStatusBorder" Height="50" Visibility="{Binding Path=AlertBarStatus.Visible, Converter={StaticResource BoolToVis}}">
I then used a timer with an event which fired after 10 seconds which set the visibility variable to false and disabled the timer which worked like a charm.
Timer code:
Timer _alertBarTimer = new Timer(10000);
_alertBarTimer.Elapsed += _alert_timer_elapsed;
private void _alert_timer_elapsed(object sender, ElapsedEventArgs e)
{
alertBarControl.AlertBarStatus.Visible = false;
_alertBarTimer.Enabled = false;
}
Code which sets the visibility:
alertBarControl.AlertBarStatus.Visible = true;
_alertBarTimer.Enabled = true;
This is probably not the best way to do it but it certainly isn't the worst as I'm not directly setting the property of the control in code but instead I am using the object bound to the control.
If anyone does have a better way of doing this please feel free to contribute :)
Related
I have a WPF application where I need to use Tab Control. In my Tab Control I have total three tab items.
Here my MainWindow.xaml code :
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
<RowDefinition Height="50"/>
</Grid.RowDefinitions>
<TabControl
Grid.Row="0"
x:Name="TestTabs">
<TabItem Header="Section 1"/>
<TabItem Header="Section 2"/>
<TabItem Header="Section 3"/>
</TabControl>
</Grid>
Due to my design purpose I need to underline the TabItem whenever it is selected. To give more design to the UI, I add some Data trigger animation into it. My approach is, I give two rectangles under the first and second Tab item and bind With Tab Control selected index. So, every time any individual Tab item is selected the Enter animation and Exit animation will fired/execute. I do not give any rectangle under the third tab item because the previous two rectangles will cover tho whole animation of those three tab items and it's sufficient.
But the problem is, the Rectangles Enter animations and Exit animations are interfere with each other and both are execute at a same time when I click any specific tab item bounded to the tab control selected index.
To overcome this issue I implement a View Model. Using this View Model I can track the last two tab items which I selected. To verify that my View Model is perfectly working or not. I tested with a Button. If I click the button then a Message Box will appear and it will show my current and Previous tab item which I selected.
But I don't know How to hide and show Rectangles depending on the last two tab items which I selected through the View Model. My approach is only to show the rectangles that are bounded to their Tab items. I give an example suppose the first Tab item named "Section 1" has a Rectangle named Rect1 and the second Tab item named "Section 2" has a Rectangle named Rect2. this Rect1 and Rect2 are only visible if their parent tab item is selected through under the View Model.
Here is my Complete MainWindow.xaml code :
<Window.Resources>
<local:TestViewModel x:Key="MainViewModel"/>
</Window.Resources>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
<RowDefinition Height="50"/>
</Grid.RowDefinitions>
<TabControl DataContext="{StaticResource MainViewModel}"
SelectedIndex="{Binding Selected}"
Grid.Row="0"
x:Name="TestTabs">
<TabItem Header="Section 1"/>
<TabItem Header="Section 2"/>
<TabItem Header="Section 3"/>
</TabControl>
<Button Content="Check
Selected Index"
Grid.Row="1"
x:Name="TestButton"
Click="TestButton_OnClick"/>
<DockPanel x:Name="rp" Grid.Row="0" LastChildFill="False" HorizontalAlignment="Stretch">
<Canvas DockPanel.Dock="Left" >
<Rectangle x:Name="Rect1" Fill="#ff0000" VerticalAlignment="Top" Width="50.2" Height="4" Margin="6,25,0,0" SnapsToDevicePixels="True" UseLayoutRounding="True" RenderOptions.EdgeMode="Aliased" RenderOptions.BitmapScalingMode="HighQuality" >
<Rectangle.Style>
<Style TargetType="{x:Type Rectangle}">
<Setter Property="SnapsToDevicePixels" Value="True"/>
<Setter Property="RenderOptions.EdgeMode" Value="Aliased"/>
<Setter Property="Visibility" Value="Visible"/>
<Setter Property="IsEnabled" Value="True"/>
<Style.Triggers>
<DataTrigger Binding="{Binding ElementName=TestTabs, Path=SelectedIndex}" Value="1">
<Setter Property="SnapsToDevicePixels" Value="True"/>
<Setter Property="RenderOptions.EdgeMode" Value="Aliased"/>
<Setter Property="Visibility" Value="Visible" />
<Setter Property="IsEnabled" Value="True" />
<DataTrigger.EnterActions>
<RemoveStoryboard BeginStoryboardName="MyBeginStoryboard3"/>
<RemoveStoryboard BeginStoryboardName="MyBeginStoryboard4"/>
<BeginStoryboard Name="MyBeginStoryboard1">
<Storyboard>
<DoubleAnimation Storyboard.TargetProperty="(Canvas.Left)" From="0" To="63.5" Duration="0:0:0.2"></DoubleAnimation>
<DoubleAnimation Storyboard.TargetProperty="Width" From="50.2" To="50.2" Duration="0:0:0.2"></DoubleAnimation>
</Storyboard>
</BeginStoryboard>
</DataTrigger.EnterActions>
<DataTrigger.ExitActions>
<BeginStoryboard Name="MyBeginStoryboard2">
<Storyboard>
<DoubleAnimation Storyboard.TargetProperty="(Canvas.Left)" From="63.5" To="0" Duration="0:0:0.2"></DoubleAnimation>
<DoubleAnimation Storyboard.TargetProperty="Width" From="50.2" To="50.2" Duration="0:0:0.2"></DoubleAnimation>
</Storyboard>
</BeginStoryboard>
</DataTrigger.ExitActions>
</DataTrigger>
<MultiDataTrigger>
<MultiDataTrigger.Conditions>
<Condition Binding="{Binding ElementName=TestTabs, Path=SelectedIndex}" Value="2"/>
</MultiDataTrigger.Conditions>
<MultiDataTrigger.EnterActions>
<RemoveStoryboard BeginStoryboardName="MyBeginStoryboard1"/>
<RemoveStoryboard BeginStoryboardName="MyBeginStoryboard2"/>
<BeginStoryboard Name="MyBeginStoryboard3">
<Storyboard>
<DoubleAnimation Storyboard.TargetProperty="(Canvas.Left)" From="0" To="123.5" Duration="0:0:0.2"></DoubleAnimation>
<DoubleAnimation Storyboard.TargetProperty="Width" From="50.2" To="50.2" Duration="0:0:0.2"></DoubleAnimation>
</Storyboard>
</BeginStoryboard>
</MultiDataTrigger.EnterActions>
<MultiDataTrigger.ExitActions>
<BeginStoryboard Name="MyBeginStoryboard4">
<Storyboard>
<DoubleAnimation Storyboard.TargetProperty="(Canvas.Left)" From="123.5" To="0" Duration="0:0:0.2"></DoubleAnimation>
<DoubleAnimation Storyboard.TargetProperty="Width" From="50.2" To="50.2" Duration="0:0:0.2"></DoubleAnimation>
</Storyboard>
</BeginStoryboard>
</MultiDataTrigger.ExitActions>
</MultiDataTrigger>
</Style.Triggers>
</Style>
</Rectangle.Style>
</Rectangle>
<Rectangle x:Name="Rect2" Fill="#ff0000" VerticalAlignment="Top" Width="50.2" Height="4" Margin="68,25,0,0" SnapsToDevicePixels="True" UseLayoutRounding="True" RenderOptions.EdgeMode="Aliased" RenderOptions.BitmapScalingMode="HighQuality" >
<Rectangle.Style>
<Style TargetType="{x:Type Rectangle}">
<Setter Property="SnapsToDevicePixels" Value="True"/>
<Setter Property="RenderOptions.EdgeMode" Value="Aliased"/>
<Setter Property="Visibility" Value="Visible"/>
<Setter Property="IsEnabled" Value="True"/>
<Style.Triggers>
<DataTrigger Binding="{Binding ElementName=TestTabs, Path=SelectedIndex}" Value="2">
<Setter Property="SnapsToDevicePixels" Value="True"/>
<Setter Property="RenderOptions.EdgeMode" Value="Aliased"/>
<Setter Property="Visibility" Value="Visible" />
<Setter Property="IsEnabled" Value="True" />
<DataTrigger.EnterActions>
<BeginStoryboard Name="MyBeginStoryboard5">
<Storyboard>
<DoubleAnimation Storyboard.TargetProperty="(Canvas.Left)" From="0" To="63.5" Duration="0:0:0.2"></DoubleAnimation>
<DoubleAnimation Storyboard.TargetProperty="Width" From="50.2" To="50.2" Duration="0:0:0.2"></DoubleAnimation>
</Storyboard>
</BeginStoryboard>
</DataTrigger.EnterActions>
<DataTrigger.ExitActions>
<BeginStoryboard Name="MyBeginStoryboard6">
<Storyboard>
<DoubleAnimation Storyboard.TargetProperty="(Canvas.Left)" From="63.5" To="0" Duration="0:0:0.2"></DoubleAnimation>
<DoubleAnimation Storyboard.TargetProperty="Width" From="50.2" To="50.2" Duration="0:0:0.2"></DoubleAnimation>
</Storyboard>
</BeginStoryboard>
</DataTrigger.ExitActions>
</DataTrigger>
</Style.Triggers>
</Style>
</Rectangle.Style>
</Rectangle>
</Canvas>
</DockPanel>
</Grid>
Here is my Main View Model code name as TestViewModel :
class TestViewModel : INotifyPropertyChanged
{
private int _selected;
public int Selected
{
get { return _selected; }
set
{
int temp = _selected;
_selected = value;
_previousSelected = temp;
NotifyPropertyChanged("Selected", temp, value);
NotifyPropertyChanged("PreviousSelected", temp, temp);
}
}
int _previousSelected = 0;
public int PreviousSelected
{
get { return _previousSelected; }
}
public event PropertyChangedEventHandler PropertyChanged;
protected void NotifyPropertyChanged<T>(string propertyName, T oldvalue, T newvalue)
{
OnPropertyChanged(this, new PropertyChangedExtendedEventArgs<T>(propertyName, oldvalue, newvalue));
}
public virtual void OnPropertyChanged(object sender, PropertyChangedEventArgs e)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
handler(sender, e);
}
}
public class PropertyChangedExtendedEventArgs<T> : PropertyChangedEventArgs
{
public virtual T OldValue { get; private set; }
public virtual T NewValue { get; private set; }
public PropertyChangedExtendedEventArgs(string propertyName, T oldValue, T newValue)
: base(propertyName)
{
OldValue = oldValue;
NewValue = newValue;
}
}
Here is the MainWindow.xaml.cs logic code where I tested the View model with a Button and Message Box to see if the last two item selection is perfectly working or not.
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
private void TestButton_OnClick(object sender, RoutedEventArgs e)
{
var vm = TestTabs.DataContext as TestViewModel;
MessageBox.Show(string.Format("You selected tab {0} ,previous selected tab{1}", vm.Selected, vm.PreviousSelected));
}
}
I don't know that my approach is correct or not that means hiding or showing the rectangles depending on that View Model is a good idea or not but I need some similar solution and it's may be different that means the rectangles are only trigger their animation depending on View model. But the answer is almost same. All type of suggestion are accepted.
I attach a GIF so that people understand that this effect I want to get in my TabControl on Tab Item selection. I know it can be done easily with code behind but I want to get solution in pure XAML and MVVM base.
I have a scenario where two lists are to be shown in one window, but user can select in any one of the list only and proceed.
Lets say listbox1 and listbox2 if user select an item in listbox1 and again select from listbox2 the selected item in listbox1 should be removed.
I have to achieve this only through xaml not from codebehind. so I tried below:
<Grid.Resources>
<Storyboard x:Key="temp" >
<Int32Animation Storyboard.TargetName="lstbox" Storyboard.TargetProperty="(ListBox.SelectedIndex)"
To="-1" Duration="0:0:.2" />
</Storyboard>
<Storyboard x:Key="temp1" >
<Int32Animation Storyboard.TargetName="listBox1" Storyboard.TargetProperty="(ListBox.SelectedIndex)"
To="-1" Duration="0:0:.2" />
</Storyboard>
</Grid.Resources>
As above created two storyboards to set selectedIndex to -1 and called that story board when selection changed event trigger as shown below:
<ListBox Name="listBox1" HorizontalAlignment="Left" Grid.Column="1">
<ListBox.Triggers>
<EventTrigger RoutedEvent="ListBox.SelectionChanged">
<BeginStoryboard Storyboard="{StaticResource temp}"/>
</EventTrigger>
</ListBox.Triggers>
<ListBox.ItemContainerStyle>
<Style TargetType="ListBoxItem">
<Setter Property="Background" Value="AliceBlue" />
<Setter Property="BorderBrush" Value="BlanchedAlmond" />
<Setter Property="BorderThickness" Value="2" />
<Style.Triggers>
<Trigger Property="IsSelected" Value="True">
<Setter Property="Foreground" Value="Red" />
</Trigger>
</Style.Triggers>
</Style>
</ListBox.ItemContainerStyle>
<ListBoxItem Content="Coffie"></ListBoxItem>
<ListBoxItem Content="Tea"></ListBoxItem>
<ListBoxItem Content="Orange Juice"></ListBoxItem>
<ListBoxItem Content="Milk"></ListBoxItem>
<ListBoxItem Content="Iced Tea"></ListBoxItem>
<ListBoxItem Content="Mango Shake"></ListBoxItem>
</ListBox>
<ListBox Name="lstbox" ItemsSource="{Binding MyData}" Grid.Column="1" Grid.Row="1" SelectionChanged="lstbox_SelectionChanged">
<ListBox.Triggers>
<EventTrigger RoutedEvent="ListBox.SelectionChanged">
<BeginStoryboard Storyboard="{StaticResource temp1}"/>
</EventTrigger>
</ListBox.Triggers>
<ListBox.ItemContainerStyle>
<Style TargetType="ListBoxItem">
<Setter Property="Background" Value="AliceBlue" />
<Setter Property="BorderBrush" Value="BlanchedAlmond" />
<Setter Property="BorderThickness" Value="2" />
<Style.Triggers>
<Trigger Property="IsSelected" Value="True">
<Setter Property="Foreground" Value="DarkViolet" />
</Trigger>
</Style.Triggers>
</Style>
</ListBox.ItemContainerStyle>
</ListBox>
Through code behind when I set SelectedIndex to -1 styles for selectedItem are clearing but through storyboard the styles are not clearing.
Please tell anyother way or guide me to make it work.
I have to achieve it only through xaml.
Thanks,
Nagasree.
Based on your sample, if you change your storyboards like so, it will work:
<Storyboard x:Key="temp">
<ObjectAnimationUsingKeyFrames
Storyboard.TargetName="lstbox"
Storyboard.TargetProperty="SelectedItem">
<ObjectAnimationUsingKeyFrames.KeyFrames>
<DiscreteObjectKeyFrame
KeyTime="0:0:0"
Value="null" />
</ObjectAnimationUsingKeyFrames.KeyFrames>
</ObjectAnimationUsingKeyFrames>
</Storyboard>
<Storyboard x:Key="temp1">
<ObjectAnimationUsingKeyFrames
Storyboard.TargetName="listBox1"
Storyboard.TargetProperty="SelectedItem">
<ObjectAnimationUsingKeyFrames.KeyFrames>
<DiscreteObjectKeyFrame
KeyTime="0:0:0"
Value="null" />
</ObjectAnimationUsingKeyFrames.KeyFrames>
</ObjectAnimationUsingKeyFrames>
</Storyboard>
However, like commenters mentioned. If you are using MVVM and databinding to SelectedItem, etc., this may not work.
If your boss will let you use a Behavior, you can do something like this:
<ListBox
Name="ListBox1"
ItemsSource="{Binding MyData1}"
SelectedItem="{Binding SelectedMyData1, Mode=TwoWay}">
<i:Interaction.Behaviors>
<local:ListBoxSingleSelectionBehavior TargetListBox="{Binding ElementName=ListBox2}" />
</i:Interaction.Behaviors>
<ListBox.ItemContainerStyle>
<Style TargetType="ListBoxItem">
<Setter Property="Background" Value="AliceBlue" />
<Setter Property="BorderBrush" Value="BlanchedAlmond" />
<Setter Property="BorderThickness" Value="2" />
<Style.Triggers>
<Trigger Property="IsSelected" Value="True">
<Setter Property="Foreground" Value="Red" />
</Trigger>
</Style.Triggers>
</Style>
</ListBox.ItemContainerStyle>
</ListBox>
<ListBox
Name="ListBox2"
ItemsSource="{Binding MyData2}"
SelectedItem="{Binding SelectedMyData2, Mode=TwoWay}">
<ListBox.ItemContainerStyle>
<Style TargetType="ListBoxItem">
<Setter Property="Background" Value="AliceBlue" />
<Setter Property="BorderBrush" Value="BlanchedAlmond" />
<Setter Property="BorderThickness" Value="2" />
<Style.Triggers>
<Trigger Property="IsSelected" Value="True">
<Setter Property="Foreground" Value="DarkViolet" />
</Trigger>
</Style.Triggers>
</Style>
</ListBox.ItemContainerStyle>
</ListBox>
And here is the behavior:
using System.Windows;
using System.Windows.Controls;
using Microsoft.Xaml.Behaviors;
namespace SO
{
public class ListBoxSingleSelectionBehavior : Behavior<ListBox>
{
private bool isSelectionChanging;
public ListBoxSingleSelectionBehavior()
{
}
#region TargetListBox (DependencyProperty)
/// <summary>
/// Identifies the TargetListBox property.
/// </summary>
public static readonly DependencyProperty TargetListBoxProperty =
DependencyProperty.Register(
"TargetListBox",
typeof(ListBox),
typeof(ListBoxSingleSelectionBehavior),
new PropertyMetadata(null, OnTargetListBoxChanged));
/// <summary>
/// Gets or sets the target ListBox to monitor.
/// </summary>
public ListBox TargetListBox
{
get => (ListBox)GetValue(TargetListBoxProperty);
set => SetValue(TargetListBoxProperty, value);
}
private static void OnTargetListBoxChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var behavior = (ListBoxSingleSelectionBehavior) d;
var oldTargetListBox = (ListBox) e.OldValue;
var newTargetListBox = (ListBox) e.NewValue;
behavior.OnTargetListBoxChanged(oldTargetListBox, newTargetListBox);
}
private void OnTargetListBoxChanged(ListBox oldTargetListBox, ListBox newTargetListBox)
{
if (oldTargetListBox != null)
{
oldTargetListBox.SelectionChanged -= OnTargetSelectionChanged;
}
if (newTargetListBox != null)
{
newTargetListBox.SelectionChanged += OnTargetSelectionChanged;
}
}
#endregion
protected override void OnAttached()
{
base.OnAttached();
AssociatedObject.SelectionChanged += OnSourceSelectionChanged;
}
protected override void OnDetaching()
{
base.OnDetaching();
if (AssociatedObject != null)
{
AssociatedObject.SelectionChanged -= OnSourceSelectionChanged;
}
}
private void OnSourceSelectionChanged(object sender, SelectionChangedEventArgs e)
{
if (isSelectionChanging) return;
isSelectionChanging = true;
if (TargetListBox != null)
{
TargetListBox.SelectedItem = null;
}
isSelectionChanging = false;
}
private void OnTargetSelectionChanged(object sender, SelectionChangedEventArgs e)
{
if (isSelectionChanging) return;
isSelectionChanging = true;
if (AssociatedObject != null)
{
AssociatedObject.SelectedItem = null;
}
isSelectionChanging = false;
}
}
}
So forgive me ahead of time if this has been answered before and I just haven't been able to see it for the life of me, but I have been searching for the past 4 hours with no avail.
I am trying to get a DataTrigger to fire on the change of a single bool property. The DataTrigger is to begin a storyboard that simply just adjusts the window size.
Here is the XAML:
<Window.Resources>
<local:SatisfactionsViewModel x:Key="SatisfactionsViewModel"/>
<!--The storyboard animation-->
<Storyboard x:Key="windowFadeUp">
<DoubleAnimationUsingKeyFrames Storyboard.TargetProperty="(FrameworkElement.Height)" Storyboard.TargetName="Satisfaction_Processing">
<EasingDoubleKeyFrame KeyTime="0:0:0.5" Value="171"/>
</DoubleAnimationUsingKeyFrames>
</Storyboard>
</Window.Resources>
<!--The Datatrigger that doesn't seem to work, probably my own stupidity-->
<Window.Style>
<Style>
<Style.Triggers>
<DataTrigger Binding="{Binding Path=IsWorkingOnStuff, NotifyOnSourceUpdated=True}" Value="true">
<DataTrigger.EnterActions>
<BeginStoryboard Storyboard="{StaticResource windowFadeUp}"/>
</DataTrigger.EnterActions>
</DataTrigger>
</Style.Triggers>
</Style>
</Window.Style>
Here is the relevant C# from the ViewModel:
private bool isWorkingOnStuff;
public bool IsWorkingOnStuff
{
get { return isWorkingOnStuff; }
set
{
if (value == isWorkingOnStuff)
{ return; }
isWorkingOnStuff = value;
OnPropertyChanged();
}
}
//Other non-relative stuff
//PropertyChangedEvent handler
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged([CallerMemberName]string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
//Bunch of other stuff not related
private void ProcessCommand()
{
if (fileName == null || fileName == "")
{
MessageBox.Show("Please select a file. \nIt won't work without one... -_-");
}
else
{
IsWorkingOnStuff = true;
IsProcessEnabled = false;
IsProgressBarVisibile = Visibility.Visible;
//more business logic goes here...
}
}
So as I understand it, when the IsWorkingOnStuff property changes to true, the datatrigger should fire, the storyboard execute, and rejoicing happen...but it's not.
Any idea where I am going wrong?
Thank you ahead of time.
Remove the Storyboard.TargetName attribute and make sure that you have set the DataContext of the window to an instance of the SatisfactionsViewModel class:
<Window x:Class="WpfApplication1.MainWindow"
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:WpfApplication1"
mc:Ignorable="d"
Title="MainWindow" Height="300" Width="300" x:Name="Satisfaction_Processing">
<Window.DataContext>
<local:SatisfactionsViewModel />
</Window.DataContext>
<Window.Resources>
<!--The storyboard animation-->
<Storyboard x:Key="windowFadeUp">
<DoubleAnimationUsingKeyFrames Storyboard.TargetProperty="(FrameworkElement.Height)">
<EasingDoubleKeyFrame KeyTime="0:0:0.5" Value="171"/>
</DoubleAnimationUsingKeyFrames>
</Storyboard>
</Window.Resources>
<Window.Style>
<Style>
<Style.Triggers>
<DataTrigger Binding="{Binding Path=IsWorkingOnStuff, NotifyOnSourceUpdated=True}" Value="true">
<DataTrigger.EnterActions>
<BeginStoryboard Storyboard="{StaticResource windowFadeUp}"/>
</DataTrigger.EnterActions>
</DataTrigger>
</Style.Triggers>
</Style>
</Window.Style>
<StackPanel>
</StackPanel>
</Window>
I have got some TextBlocks with x:Name, i'm able to access this control easily with C#. However I have also got some DoubleAnimations with an x:Name but unable to access these, why is this?
I have tried this but it does not work, I get an error saying Object reference not set to an instance of an object:
//Add completed methods
DoubleAnimation da1 = (DoubleAnimation)Resources["storyboardLoad"];
da1.Completed += DoubleAnimationCompleted;
Notifications.xaml
<Window x:Class="PhotoManagement.Views.Notification"
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:PhotoManagement.Views"
mc:Ignorable="d"
Title="NotificationWindow" WindowStyle="None" AllowsTransparency="True" Background="Transparent" Width="350" SizeToContent="Height">
<Window.Resources>
<FontFamily x:Key="FontAwesome">/Fonts/#FontAwesome</FontFamily>
<SolidColorBrush x:Key="colour1" Color="#29a1d5"/>
<SolidColorBrush x:Key="colour2" Color="#248eb8"/>
<Style TargetType="StackPanel">
<Setter Property="Margin" Value="20" />
</Style>
<Style TargetType="TextBlock">
<Setter Property="TextWrapping" Value="Wrap" />
<Setter Property="Margin" Value="5" />
</Style>
<Style x:Key="NotificationIcon" TargetType="Border">
<Setter Property="HorizontalAlignment" Value="Center" />
<Setter Property="VerticalAlignment" Value="Center" />
<Setter Property="Background" Value="{DynamicResource colour2}" />
<Style.Resources>
<Style TargetType="TextBlock">
<Setter Property="FontFamily" Value="{StaticResource FontAwesome}" />
<Setter Property="Foreground" Value="White" />
<Setter Property="FontSize" Value="28" />
<Setter Property="Padding" Value="15" />
</Style>
</Style.Resources>
</Style>
<Style x:Key="NotificationText" TargetType="Border">
<Setter Property="VerticalAlignment" Value="Stretch" />
<Setter Property="HorizontalAlignment" Value="Stretch" />
<Setter Property="Background" Value="{DynamicResource colour1}" />
<Style.Resources>
<Style TargetType="TextBlock">
<Setter Property="Foreground" Value="White" />
<Setter Property="VerticalAlignment" Value="Center" />
<Setter Property="HorizontalAlignment" Value="Center" />
<Setter Property="TextWrapping" Value="Wrap" />
</Style>
</Style.Resources>
</Style>
<Style x:Key="NotificationContainer" TargetType="Grid">
<!-- Animation -->
<Style.Triggers>
<EventTrigger RoutedEvent="Window.Loaded">
<BeginStoryboard x:Name="StoryboardLoad">
<Storyboard>
<DoubleAnimation Storyboard.TargetProperty="(UIElement.Opacity)"
From="0.0"
To="1.0"
Duration="0:0:0.5"/>
<DoubleAnimation x:Name="storyboardLoad" Storyboard.TargetProperty="(UIElement.Opacity)"
From="1.0"
To="0.0"
Duration="0:0:1"
BeginTime="0:0:2" />
</Storyboard>
</BeginStoryboard>
</EventTrigger>
<EventTrigger RoutedEvent="Mouse.MouseEnter">
<EventTrigger.Actions>
<RemoveStoryboard BeginStoryboardName="StoryboardLoad"/>
<RemoveStoryboard BeginStoryboardName="StoryboardFade"/>
</EventTrigger.Actions>
</EventTrigger>
<EventTrigger RoutedEvent="Mouse.MouseLeave">
<BeginStoryboard x:Name="StoryboardFade">
<Storyboard>
<DoubleAnimation x:Name="storyboardFade"
Storyboard.TargetProperty="(UIElement.Opacity)"
From="1.0"
To="0.0"
Duration="0:0:1"
BeginTime="0:0:2" />
</Storyboard>
</BeginStoryboard>
</EventTrigger>
</Style.Triggers>
</Style>
</Window.Resources>
<Grid Style="{StaticResource NotificationContainer}">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="50"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Border Grid.Column="1"
CornerRadius="0 5 5 0"
Style="{StaticResource NotificationText}">
<TextBlock Text="A request has been added!"
x:Name="NotificationText"/>
</Border>
<Border Grid.Column="0"
CornerRadius="5 0 0 5"
Style="{StaticResource NotificationIcon}">
<TextBlock Text=""
x:Name="NotificationIcon"/>
</Border>
</Grid>
<Grid.RenderTransform>
<ScaleTransform ScaleY="1" />
</Grid.RenderTransform>
</Grid>
</Window>
Notifications.xaml.cs
using System;
using System.Windows;
namespace PhotoManagement.Views
{
/// <summary>
/// Interaction logic for Notification.xaml
/// </summary>
public partial class Notification : Window
{
public Notification() : base()
{
InitializeComponent();
//Move other windows down when closed
this.Closed += this.NotificationWindowClosed;
//Get object properties
this.NotificationText.Text = "Hello";
this.NotificationIcon.Text = "G";
}
/// <summary>
/// Show the Notification and setup it's content and actions
/// </summary>
public new void Show()
{
this.Topmost = true;
this.Owner = System.Windows.Application.Current.MainWindow;
this.Closed += this.NotificationWindowClosed;
base.Show();
//Add completed methods
//Position the Notification
var workingArea = SystemParameters.WorkArea;
this.Left = (workingArea.Width - this.ActualWidth) / 2;
double top = workingArea.Bottom - this.ActualHeight - 5;
//Ensure new notifications are placed above older ones
foreach (Window window in System.Windows.Application.Current.Windows)
{
string windowName = window.GetType().Name;
if (windowName.Equals("Notification") && window != this)
{
window.Topmost = true;
top = window.Top - window.ActualHeight - 5;
}
}
this.Top = top;
}
private void ImageMouseUp(object sender,
System.Windows.Input.MouseButtonEventArgs e)
{
this.Close();
}
/// <summary>
/// Close window once animation is complete
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
public void DoubleAnimationCompleted(object sender, EventArgs e)
{
if (!this.IsMouseOver)
{
this.Close();
}
}
/// <summary>
/// Move the next notification down once notification has closed
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void NotificationWindowClosed(object sender, EventArgs e)
{
foreach (Window window in System.Windows.Application.Current.Windows)
{
string windowName = window.GetType().Name;
if (windowName.Equals("NotificationWindow") && window != this)
{
// Adjust any windows that were above this one to drop down
if (window.Top < this.Top)
{
window.Top = window.Top + this.ActualHeight;
}
}
}
}
}
}
When you create a custom Window or UserControl, a (partial) class is generated for it that contains fields for any named child elements. That won't work for styles and templates however - likely because those can be applied to multiple controls, so a name doesn't necessarily map to a single item (or to an item at all).
The hard way
So the animation you're looking for is part of a style, which is a resource in your window. To be precise: your style has a collection of triggers. Its first trigger has a 'begin storyboard' action, and that action holds the storyboard you're interested in. It's going to take some searching and type-casting to get there:
Style style = (Style)Resources["NotificationContainer"];
EventTrigger trigger = (EventTrigger)style.Triggers[0];
BeginStoryboard beginStoryboard = (BeginStoryboard)trigger.Actions[0];
Storyboard storyboard = beginStoryboard.Storyboard;
As you can see, that's quite brittle. All you need to do to break it is reordering the event triggers in XAML...
The easy way
But why look up that storyboard anyway? Just bind its Completed event to an event handler in XAML directly:
<Storyboard Completed="WindowLoadedStoryboard_Completed">
...
</Storyboard>
Sadly, that's not going to work directly due to a "The event 'Completed' cannot be specified on a Target tag in a Style. Use an EventSetter instead." error. And an EventSetter is not going to work because Storyboard.Completed is not a routed event...
Fortunately, you can work around that by making the storyboard itself a resource, and referencing it in the begin-storyboard action:
<Resources>
<Storyboard x:Key="WindowLoadedStoryboard" Completed="WindowLoadedStoryboard_Completed">
...
</Storyboard>
<Style ...>
...
<BeginStoryboard Storyboard="{StaticResource WindowLoadedStoryboard}" />
...
</Style>
</Resources>
Which, incidentally, would also make looking up that storyboard easier and more reliable.
I have a situation as follows
I want to hide delete button when no item is selected & show when something is selected.
Also hide that button if user selects an item & clicks elsewhere (which represents that user is no longer working with the listbox).
I tried LostFocus events, checking SelectedIndex etc. but no success. Any idea how to do this?
private void ListBoxItem_LostFocus(object sender, RoutedEventArgs e)
{
if (button.IsFocused != true) // checking if user has selected an item & clicking on button (valid action)
{
listbox.SelectedIndex = -1;
}
}
private void listbox_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
if (listbox.SelectedIndex == -1)
{
button.Visibility = Visibility.Hidden;
}
else
{
button.Visibility = Visibility.Visible;
}
}
Joseph's answer is correct but you don't need a converter.
You can achieve the same result with a simple trigger:
<ListBox>
...
<ListBox.Style>
<Style TargetType="{x:Type ListBox}">
<Style.Triggers>
<Trigger Property="SelectedItem" Value="{x:Null}">
<Setter TargetName="YOUR_BUTTON'S_NAME" Property="Visibility" Value="Hidden"/>
</Trigger>
</Style.Triggers>
</Style>
</ListBox.Style>
</ListBox>
Edit: It's indeed impossible to use TargetName in a Style Setter.
So, the closest solution I could make work is to create a DataTrigger:
<DockPanel LastChildFill="True">
<Button DockPanel.Dock="Top">
<Button.Style>
<Style TargetType="{x:Type Button}">
<Style.Triggers>
<DataTrigger Binding="{Binding ElementName=MyList, Path=SelectedItem}"
Value="{x:Null}">
<Setter Property="Visibility" Value="Hidden"/>
</DataTrigger>
</Style.Triggers>
</Style>
</Button.Style>
<TextBlock Text="Button"/>
</Button>
<ListBox x:Name="MyList">
<TextBlock Text="Something"/>
<TextBlock Text="Something"/>
<TextBlock Text="Something"/>
<TextBlock Text="Something"/>
<TextBlock Text="Something"/>
</ListBox>
</DockPanel>
For the first half of the question the best way is to use a converter like so :
1. add a Converter class :
public class VisibilityConverter:IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
return (value == null ? Visibility.Hidden : Visibility.Visible);
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
2.add it to the Window resources:
<Window.Resources>
<wpfApplication:VisibilityConverter x:Key="VisibilityConverter"/>
</Window.Resources>
3. and use it to hide/show your button :
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<ListBox HorizontalAlignment="Center" x:Name="listBox">
<ListBoxItem Content="Fist"/>
<ListBoxItem Content="Second"/>
<ListBoxItem Content="Third"/>
<ListBoxItem Content="Fourth"/>
</ListBox>
<Button Content="delete" Grid.Row="1" HorizontalAlignment="Center" Visibility="{Binding SelectedItem,ElementName=listBox,Converter={StaticResource VisibilityConverter}}"/>
</Grid>
And for the second half of the question, you must handle the MouseDown event in your window:
MouseDown="MainWindow_OnMouseDown"
and add the following handler (that will reset the SelectedItem of the list to null)
private void MainWindow_OnMouseDown(object sender, MouseButtonEventArgs e)
{
listBox.SelectedItem = null;
}
Run this code separately and try.it is working for me here.
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition></ColumnDefinition>
<ColumnDefinition></ColumnDefinition>
</Grid.ColumnDefinitions>
<StackPanel>
<ListBox x:Name="lst">
<ListBox.Triggers>
<EventTrigger RoutedEvent="GotFocus">
<BeginStoryboard>
<Storyboard>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="DeleteButton" Storyboard.TargetProperty="Visibility">
<DiscreteObjectKeyFrame KeyTime="0:0:0.1" Value="{x:Static Visibility.Visible}"/>
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</BeginStoryboard>
</EventTrigger>
<EventTrigger RoutedEvent="LostFocus">
<BeginStoryboard>
<Storyboard>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="DeleteButton" Storyboard.TargetProperty="Visibility">
<DiscreteObjectKeyFrame KeyTime="0:0:0.1" Value="{x:Static Visibility.Collapsed}"/>
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</BeginStoryboard>
</EventTrigger>
</ListBox.Triggers>
<ListBoxItem>ListBoxItem1</ListBoxItem>
<ListBoxItem>ListBoxItem2</ListBoxItem>
<ListBoxItem>ListBoxItem3</ListBoxItem>
<ListBoxItem>ListBoxItem4</ListBoxItem>
</ListBox>
<Button x:Name="DeleteButton" Content="Delete" Height="30" Width="100">
<Button.Style>
<Style BasedOn="{StaticResource alredayDefinedStyleKey}" TargetType="Button">
<Setter Property="Visibility" Value="Visible"></Setter>
<Style.Triggers>
<DataTrigger Binding="{Binding Path=SelectedItem, ElementName=lst}" Value="{x:Null}" >
<Setter Property="Visibility" Value="Collapsed"></Setter>
</DataTrigger>
</Style.Triggers>
</Style>
</Button.Style>
</Button>
</StackPanel>
<ListBox Grid.Column="1">
<ListBoxItem>1</ListBoxItem>
<ListBoxItem>2</ListBoxItem>
</ListBox>
</Grid>
LostFocus will only fire, if the element loses Logical focus. Use LostKeyboardFocus event. Then you're one step closer.