I want to trigger an animation whenever a property is set (say "StatusMessages") in my ViewModel. The developer has already had the event set up:
eventAggregator.GetEvent<ShowStatusEvent>().Subscribe(
(message) => ShowStatus(message), ThreadOption.UIThread
);
private void ShowStatus(MyApp.MyModelViews.StatusMessage statusMessage){
// set some values in status message view model
}
What I don't know is what would be the Prism way of hooking up the event with my View (preferably in XAML) so that it triggers the animation. The following "DataTrigger" only works (i.e., triggers the animation) when the source is evaluated to a certain value, say changed from "Debug" to "Error". So if the value is set 12 times but to the same value, say "Debug" each time, the datatrigger only happens at most one time (from default to the new value, assuming they are not equal).
<DataTrigger Binding="{Binding DataContext.StatusMessages, ElementName=MyPanel}" Value="Error">...</DataTrigger>
The workaround now we came up with is to have a new bool property introduced especially for triggering the animation:
public bool CanBeginStoryboard{
get
{
return canBeginStoryboard;
}
set
{
canBeginStoryboard = value;
RaisePropertyChanged(() => CanBeginStoryboard);
}
}
private void ShowStatus(MyApp.MyModelViews.StatusMessage statusMessage)
{
CanBeginStoryboard = false;
//// set some values in status message view model
CanBeginStoryboard = true;
}
<DataTrigger Binding="{Binding DataContext.CanBeginStoryboard, ElementName=MyPanel}" Value="True">
<DataTrigger.EnterActions>
<BeginStoryboard>...</BeginStoryboard>
</DataTrigger.EnterActions>
I assume there is a standard "Prism's way" of doing such things (like WPF "RoutedEvent">"EventTrigger"?), which is different from what we are doing here? BTW, we are using Prism 4. Thanks in advance.
Related
So right now I'm using a property on my control that binds to a bool property on my viewmodel called searcheditemfound (with its pair, searcheditemnotfound). I can't have just one property because even if I raise OnPropertyChanged("variableName), it will not activate the trigger unless there was an actual value change. This means that in the code for the view model, I have to do this really ugly looking:
SearchedItemNotFound = false;
SearchedItemNotFound = true;
private bool _SearchedItemNotFound;
public bool SearchedItemNotFound
{
get
{
return _SearchedItemNotFound;
}
set
{
if (value != _SearchedItemNotFound)
{
_SearchedItemNotFound = value;
OnPropertyChanged("SearchedItemNotFound");
}
}
}
when what I would really like is to just tie to an event in the view model. However, eventtriggers only trigger off routed events. Can I place routed events in the viewmodel? I think I have to inherit from control to do that.
One post here: How can I Have a WPF EventTrigger on a View trigger when the underlying Viewmodel dictates it should?
mentioned using
<i:Interaction.Triggers>
<samples:DataEventTrigger EventName="YourEvent">
<im:ControlStoryboardAction Storyboard="{StaticResource Storyboard1}"
ControlStoryboardOption="Play"/>
</samples:DataEventTrigger>
</i:Interaction.Triggers>
which looks perfect, except that it requires expression blend and I'm in visual studio 2008 where it isn't available. I'm wondering what other options I have. I don't even mind if it's not MVVM. I'm not a purist, I'm just trying to understand what my options are.
A RoutedEvent is a UI element and has no place in a view model. Personally, I can't really see the problem with changing your bool property from true to false and then back to true again... there is a valid reason for doing that. If it really bothers you, then just hide it away in a method and call that instead:
private StartSearchedItemNotFoundAnimation()
{
SearchedItemNotFound = false;
SearchedItemNotFound = true;
}
There's hardly any point in using any more complex and/or expensive ways to replicate the functionality that you already have.
Download repro project
Edit
The solution working sometimes completely threw me off my initial wondering why this is working in the first place. After all, the items aren't part of the Visual tree. In the end, it makes total sense:
The buttons in that collection aren't in the visual tree and thus element bindings don't work.
Applying the templates puts them into the visual tree and binding, if applied at this time, start working.
This confirms the suspected race condition.
A colleague of mine did some extended debugging that showed the issue as well - in the cases the binding succeeded, OnApplyBinding was invoked first. So using the collection without adjusting the logical tree was simply flawed.
Thanks for the replies that put back on the right track!
Original Post
I have a view control that exposes an ObservableCollection, My view can contain arbitrary elements, e.g. buttons. Note the ElementName binding on the button:
<local:ViperView>
<local:ViperView.MenuItems>
<Button Content="{Binding ElementName=btn, Path=Content}" />
</local:ViperView.MenuItems>
<Grid>
<Button x:Name="btn" Content="HELLO WORLD" />
</Grid>
</local:ViperView>
The control's ControlTemplate just renders the content using an ItemsControl:
<ControlTemplate ...
...
<ItemsControl
x:Name="PART_NavigationMenuItemsHost"
ItemsSource="{Binding MenuItems, RelativeSource={RelativeSource TemplatedParent}}" />
</ControlTemplate>
The view above is assigned to the ActiveView property of my main view model. The main window just displays the view via data binding.
Now the problem: The ElementName binding within that view doesn't work reliably if the view is not immediately assigned to the view model after it's creation.
ElementName bindings work like this:
MainViewModel.ActiveView = new ViperView();
ElementName bindings works sometimes using normal priority:
var view = new ViperView();
Dispatcher.BeginInvoke(() => MainViewModel.ActiveView view);
ElementName binding always fails if the view model property is set with low priority:
var view = new ViperView();
Dispatcher.BeginInvoke(DispatcherPriority.Render, () => MainViewModel.ActiveView = view);
ElementName binding sometimes works if the property is set from a worker thread (Binding engine marshalls back to the UI thread):
var view = new ViperView();
Task.Factory.StartNew(() => MainViewModel.ActiveView = view);
ElementName binding always fails if the worker thread has a delay:
var view = new ViperView();
var view = new ViperView();
Task.Factory.StartNew(() =>
{
Thread.Sleep(100);
MainViewModel.ActiveView = view;
});
I don't have an answer to this. It appears to be related to timings. For example, if I add a short Thread.Sleep to the Task sample above, this always causes the bindings to break, while without the sleep, it only sometimes breaks.
This is quite the show stopper for me - any pointer are appreciated...
Thanks for your advice
Philipp
As far as I know, ElementName binding is not updated at any time: it'll only bind to the property once and then stop updating.
This could explain your problem here: the first binding will happen (or won't) depending on the timestamp.
There is a change you can fix it by specifying the UpdateSourceTrigger property for the binding:
<Button Content="{Binding ElementName=btn, Path=Content, UpdateSourceTrigger=PropertyChanged}" />
This will make sure your binding gets updated every time btn.Content is updated.
Hope this works =)
I can't quite explain why the first option works. However I can explain why the other ones wont work.
Okay, first of all, ElementName can only work when elements are in the same visual tree. Notice that NavigationButtonItems are seperate from the actual content of ViperView.
Thus say you do:
<Button Content="{Binding ActualWidth,
RelativeSource={RelativeSource AncestorType=WpfApplication2:ViperView}}" />
(it's part of NavigationButtonItems). Now if the NavigationButtonItems and ViperView items are not blended into one(blending to one visual tree happens in ControlTemplate), then this binding would fail, and STAY as failed.
Now say the visual tree happens to be ONE as the binding is happening, then the binding will succeed and everything is nice.
Note that blending into one visual tree happens when you render the content, eg do:
dc.ActiveScreen = viperview;
Here is a quick example to demonstrate how you can do it little better:
<Button
Background="Purple"
Width="100"
Height="20"
>
<Button.Style>
<Style TargetType="Button">
<Style.Triggers>
<DataTrigger Binding="{Binding IsReady}" Value="True">
<Setter Property="Content" Value="{Binding ActualWidth,
RelativeSource={RelativeSource AncestorType=WpfApplication2:ViperView}}" />
</DataTrigger>
</Style.Triggers>
</Style>
</Button.Style>
</Button>
IsReady property should be in viewModel and it essentially tells that "YES, everything is rendered as one visual tree now, you can apply bindings."
If you do the IsReady trick, the ActualWidth will start working:
Task.Factory.StartNew(() =>
{
Thread.Sleep(10);
dc.ActiveScreen = view;
//ps you might need to force wpf finish with rendering here. like use Application.DoEvents()
dc.IsReady = true;//force bindings since everything is one now.
});
Let me know if you need clarifications.
Given that the ElementName binding in part fails before the buttons in the sample are even added to the collection of the parent view, there's not much I can do to intercept the bindings. A slightly dirty workaround would be to just refresh the bindings in those controls once the template has been applied and the visual tree established:
Fron OnApplyTemplate, invoke:
internal static class BindingUtil
{
/// <summary>
/// Recursively resets all ElementName bindings on the submitted object
/// and its children.
/// </summary>
public static void ResetElementNameBindings(this DependencyObject obj)
{
IEnumerable boundProperties = obj.GetDataBoundProperties();
foreach (DependencyProperty dp in boundProperties)
{
Binding binding = BindingOperations.GetBinding(obj, dp);
if (binding != null && !String.IsNullOrEmpty(binding.ElementName)) //binding itself should never be null, but anyway
{
//just updating source and/or target doesn’t do the trick – reset the binding
BindingOperations.ClearBinding(obj, dp);
BindingOperations.SetBinding(obj, dp, binding);
}
}
int count = VisualTreeHelper.GetChildrenCount(obj);
for (int i = 0; i < count; i++)
{
//process child items recursively
DependencyObject childObject = VisualTreeHelper.GetChild(obj, i);
ResetElementNameBindings(childObject);
}
}
public static IEnumerable GetDataBoundProperties(this DependencyObject element)
{
LocalValueEnumerator lve = element.GetLocalValueEnumerator();
while (lve.MoveNext())
{
LocalValueEntry entry = lve.Current;
if (BindingOperations.IsDataBound(element, entry.Property))
{
yield return entry.Property;
}
}
}
}
Another fix, and probably preferable, would be to change the logical tree at runtime. Adding the code below to my view solves the issue, too:
public class ViperView : ContentControl
{
private readonly ObservableCollection<object> menuItems = new ObservableCollection<object>();
public ObservableCollection<object> NavigationMenuItems
{
get { return menuItems; }
}
public ViperView()
{
NavigationMenuItems.CollectionChanged += OnMenuItemsChanged;
}
private void OnMenuItemsChanged(object sender, NotifyCollectionChangedEventArgs e)
{
if (e.NewItems != null)
{
foreach (var newItem in e.NewItems)
{
AddLogicalChild(newItem);
}
}
}
protected override IEnumerator LogicalChildren
{
get
{
yield return this.Content;
foreach (var mi in NavigationMenuItems)
{
yield return mi;
}
}
}
}
Suppose I have a ListBox bound to an ObservableCollection and I want to animate adding/removing of ListBoxItems eg. FadeIn/Out, SlideDown/Up etc. How can I do that?
After spending mad hours hunting the wilds of Google, I figure I should share how I solved this problem since it seems to be a pretty d*mn simple thing to need and yet WPF makes it ridiculously frustrating until you intimately understand how animation is implemented. Once you do, you realize FrameworkElement.Unloaded is a useless event for animation. I've seen many versions of this question all over StackOverflow (amongst others), with all sorts of hackish ways to solve this. Hopefully I can provide a most simple example that you can then fancy up for your many purposes.
I will not show Fade In example since that is covered by plenty of examples using the Loaded routed event already. It is Fading Out on item removal that is the royal pain in the *#$.
The major problem here stems from how Storyboards just get weird when you put them into Control/Data Templates / Styles. It is impossible to bind the DataContext (and thus your object's ID) to the Storyboard. The Completed event fires with zero idea of who it just finished on. Diving the visual tree is useless since all your data templated items have the same names for their containers! So sure, you could write up a function that goes and searches the entire collection for objects that have their removal flag property set, but that is ugly and honestly, just not something you ever want to admit writing on purpose. And it won't work if you have several objects being removed within the length of your animation of each other (which is my case). You could also just write a cleanup thread that does similar things and get lost in timing hell. No fun. I digress. On to the solution.
Assumptions:
You are using ObservableCollection populated with some custom objects
You use a DataTemplate to give these a custom look, hence why you want to animate their removal
You bind the ObservableCollection to a ListBox (or something simple like it)
You have INotifyPropertyChanged implemented on the class of objects in your OC.
Then the solution is quite simple really, painfully so if you spent any long amount of time trying to solve this.
Create a Storyboard that animates your fade out in the Window.Resources section of your window (above the DataTemplate).
(Optional) Define the Duration separately as a resource so you can avoid hard coding as much. Or just hard code the durations.
Make a public boolean property in your object class called "Removing", "isRemoving", whatev. Make sure you raise a Property Changed event for this field.
Create a DataTrigger that binds to your "Removing" property and on True plays the fade out storyboard.
Create a private DispatcherTimer object in your object class and implement a simple timer that has the same duration as your fade out animation and removes your object from the list in its tick handler.
Code example is below, which hopefully makes it all easy to grasp. I simplified the example as much as possible so you'll need to adapt it to your environment as it suits you.
Code Behinds
public partial class MainWindow : Window
{
public static ObservableCollection<Missiles> MissileRack = new ObservableCollection<Missiles>(); // because who doesn't love missiles?
public static Duration FadeDuration;
// main window constructor
public MainWindow()
{
InitializeComponent();
// somewhere here you'll want to tie the XAML Duration to your code-behind, or if you like ugly messes you can just skip this step and hard code away
FadeDuration = (Duration)this.Resources["cnvFadeDuration"];
//
// blah blah
//
}
public void somethread_ShootsMissiles()
{
// imagine this is running on your background worker threads (or something like it)
// however you want to flip the Removing flag on specific objects, once you do, it will fade out nicely
var missilesToShoot = MissileRack.Where(p => (complicated LINQ search routine).ToList();
foreach (var missile in missilesToShoot)
{
// fire!
missile.Removing = true;
}
}
}
public class Missiles
{
public Missiles()
{}
public bool Removing
{
get { return _removing; }
set
{
_removing = value;
OnPropertyChanged("Removing"); // assume you know how to implement this
// start timer to remove missile from the rack
start_removal_timer();
}
}
private bool _removing = false;
private DispatcherTimer remove_timer;
private void start_removal_timer()
{
remove_timer = new DispatcherTimer();
// because we set the Interval of the timer to the same length as the animation, we know the animation will finish running before remove is called. Perfect.
remove_timer.Interval = MainWindow.TrackFadeDuration.TimeSpan; // I'm sure you can find a better way to share if you don't like global statics, but I am lazy
remove_timer.Tick += new EventHandler(remove_timer_Elapsed);
remove_timer.Start();
}
// use of DispatcherTimer ensures this handler runs on the GUI thread for us
// this handler is now effectively the "Storyboard Completed" event
private void remove_timer_Elapsed(object sender, EventArgs e)
{
// this is the only operation that matters for this example, feel free to fancy this line up on your own
MainWindow.MissileRack.Remove(this); // normally this would cause your object to just *poof* before animation has played, but thanks to timer,
}
}
XAMLs
<Window
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Test" Height="300" Width="300">
<Window.Resources>
<Duration x:Key="cnvFadeDuration">0:0:0.3</Duration> <!-- or hard code this if you really must -->
<Storyboard x:Key="cnvFadeOut" >
<DoubleAnimation Storyboard.TargetName="cnvMissile"
Storyboard.TargetProperty="Opacity"
From="1" To="0" Duration="{StaticResource cnvFadeDuration}"
/>
</Storyboard>
<DataTemplate x:Key="MissileTemplate">
<Canvas x:Name="cnvMissile">
<!-- bunch of pretty missile graphics go here -->
</Canvas>
<DataTemplate.Triggers>
<DataTrigger Binding="{Binding Path=Removing}" Value="true" >
<DataTrigger.EnterActions>
<!-- you could actually just plop the storyboard right here instead of calling it as a resource, whatever suits your needs really -->
<BeginStoryboard Storyboard="{StaticResource cnvFadeOut}" />
</DataTrigger.EnterActions>
</DataTrigger>
</DataTemplate.Triggers>
</DataTemplate>
</Window.Resources>
<Grid>
<ListBox /> <!-- do your typical data binding and junk -->
</Grid>
</Window>
Huzzah!~
Dr TJ's answer is right enough. Going down that route you'd have to wrap ObservableCollection<T> and implement a BeforeDelete event,..then you could use an EventTrigger to control the storyboards.
That's a right pain though. You're probably better creating a DataTemplate and handling the FrameworkElement.Loaded and FrameworkElement.Unloaded events in an EventTrigger.
I've put a quick sample together for you below. You'd have to sort out the remove code yourself but I'm sure you're up to it.
<ListBox>
<ListBox.ItemsSource>
<x:Array Type="sys:String">
<sys:String>One</sys:String>
<sys:String>Two</sys:String>
<sys:String>Three</sys:String>
<sys:String>Four</sys:String>
<sys:String>Five</sys:String>
</x:Array>
</ListBox.ItemsSource>
<ListBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding}"
Opacity="0">
<TextBlock.Triggers>
<EventTrigger RoutedEvent="FrameworkElement.Loaded">
<BeginStoryboard>
<Storyboard>
<DoubleAnimation Storyboard.TargetProperty="Opacity"
Duration="00:00:02"
From="0"
To="1" />
</Storyboard>
</BeginStoryboard>
</EventTrigger>
<EventTrigger RoutedEvent="FrameworkElement.Unloaded">
<BeginStoryboard>
<Storyboard>
<DoubleAnimation Storyboard.TargetProperty="Opacity"
Duration="00:00:02"
From="1"
To="0" />
</Storyboard>
</BeginStoryboard>
</EventTrigger>
</TextBlock.Triggers>
</TextBlock>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
HTH, Stimul8d
Fade-out is likely to be impossible without re-writing the ItemsControl base implementation. The problem is that when the ItemsControl receives the INotifyCollectionChanged event from the collection it immediately (and within deep private code) marks the item container as not visible (IsVisible is a readonly property that gets its value from a hidden cache so cannot be accessed).
You can easily implement the fade-in in this way:
public class FadingListBox : ListBox
{
protected override void PrepareContainerForItemOverride(
DependencyObject element, object item)
{
var lb = (ListBoxItem)element;
DoubleAnimation anm = new DoubleAnimation(0, 1,
TimeSpan.FromMilliseconds(500));
lb.BeginAnimation(OpacityProperty, anm);
base.PrepareContainerForItemOverride(element, item);
}
}
But the 'fade-out' equivalent never works as the container is already invisible and cannot be reset.
public class FadingListBox : ListBox
{
protected override void ClearContainerForItemOverride(
DependencyObject element, object item)
{
var lb = (ListBoxItem) element;
lb.BringIntoView();
DoubleAnimation anm = new DoubleAnimation(
1, 0, TimeSpan.FromMilliseconds(500));
lb.BeginAnimation(OpacityProperty, anm);
base.ClearContainerForItemOverride(element, item);
}
}
Even if you have your own custom container generator, you cannot overcome this issue
protected override DependencyObject GetContainerForItemOverride()
{
return new FadingListBoxItem();
}
And this kind of makes sense, because if the container was still visible after the data it represents has disappeared, then you could theoretically click on the container (kicking off triggers, events etc) and experience some subtle bugs perhaps.
The accepted answer works for animating the addition of new items, but not for the removal of existing ones. This is because by the time the Unloaded event fires, the item has already been removed. The key to getting deletion to work is to add a "marked for deletion" concept. Being marked for deletion should trigger the animation, and the completion of the animation should trigger the actual deletion. There are probably a bunch of ways this idea could be implemented, but I got it to work by creating an attached behavior and by tweaking my viewmodels a bit. The behavior exposes three attached properties, all of which must be set on each ListViewItem:
"Storyboard" of type Storyboard. This is the actual animation you want to run when an item is removed.
"PerformRemoval" of type ICommand. This is a command that will be executed when the animation is done running. It should execute code to actually remove the element from the databound collection.
"IsMarkedForRemoval" of type bool. Set this to true when you decide to remove an item from the list (e.g. in a button click handler). As soon as the attached behavior sees this property change to true, it will begin the animation. And when the animation's Completed event fires, it will Execute the PerformRemoval command.
Here is a link to full source for the behavior and example usage (if it's bad form to direct to your own blog, I'll remove the link. I'd paste the code here, but it's fairly lengthy. I don't receive any money from the thing, if that makes a difference).
For me FrameworkElement.Unloaded event doesn't work - the item just disappears instantly. I can hardly believe that years of experience with WPF haven't produced anything prettier, but looks like the only way this can work is a hack described here: Animating removed item in Listbox ?..
Heh. Since accepted solution is not work, let's try another round ;)
We can't use Unloaded event because ListBox (or other control) remove item from visual tree when it removed from original list. So main idea is to create shadow copy of provided ObservableCollection and bind list to it.
First of all - XAML:
<ListBox ItemsSource="{Binding ShadowView}" IsSynchronizedWithCurrentItem="True">
<ListBox.ItemTemplate>
<DataTemplate>
<Border Loaded="OnItemViewLoaded">
<TextBlock Text="{Binding}"/>
</Border>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
Create ListBox, bind it to our shadow copy, set IsSynchronizedWithCurrentItem for correct support ICollectionView.CurrentItem (very useful interface), and set Loaded event on item view. This event handler need to associate view (which will be animated) and item (which will be removed).
private void OnItemViewLoaded (object sender, RoutedEventArgs e)
{
var fe = (FrameworkElement) sender ;
var dc = (DependencyObject) fe.DataContext ;
dc.SetValue (ShadowViewSource.ViewProperty, fe) ;
}
Initialize everything:
private readonly ShadowViewSource m_shadow ;
public ICollectionView ShadowView => m_shadow.View ;
public MainWindow ()
{
m_collection = new ObservableCollection<...> () ;
m_view = CollectionViewSource.GetDefaultView (m_collection) ;
m_shadow = new ShadowViewSource (m_view) ;
InitializeComponent ();
}
And last, but not least, ShadowViewSource class (yeah, it's not perfect, but as proof-of-concept it works):
using System ;
using System.Collections.Generic ;
using System.Collections.ObjectModel ;
using System.Collections.Specialized ;
using System.ComponentModel ;
using System.Linq ;
using System.Windows ;
using System.Windows.Data ;
using System.Windows.Media.Animation ;
namespace ShadowView
{
public class ShadowViewSource
{
public static readonly DependencyProperty ViewProperty = DependencyProperty.RegisterAttached ("View", typeof (FrameworkElement), typeof (ShadowViewSource)) ;
private readonly ICollectionView m_sourceView ;
private readonly IEnumerable<object> m_source ;
private readonly ICollectionView m_view ;
private readonly ObservableCollection<object> m_collection ;
public ShadowViewSource (ICollectionView view)
{
var sourceChanged = view.SourceCollection as INotifyCollectionChanged ;
if (sourceChanged == null)
throw new ArgumentNullException (nameof (sourceChanged)) ;
var sortChanged = view.SortDescriptions as INotifyCollectionChanged ;
if (sortChanged == null)
throw new ArgumentNullException (nameof (sortChanged)) ;
m_source = view.SourceCollection as IEnumerable<object> ;
if (m_source == null)
throw new ArgumentNullException (nameof (m_source)) ;
m_sourceView = view ;
m_collection = new ObservableCollection<object> (m_source) ;
m_view = CollectionViewSource.GetDefaultView (m_collection) ;
m_view.MoveCurrentTo (m_sourceView.CurrentItem) ;
m_sourceView.CurrentChanged += OnSourceCurrentChanged ;
m_view.CurrentChanged += OnViewCurrentChanged ;
sourceChanged.CollectionChanged += OnSourceCollectionChanged ;
sortChanged.CollectionChanged += OnSortChanged ;
}
private void OnSortChanged (object sender, NotifyCollectionChangedEventArgs e)
{
using (m_view.DeferRefresh ())
{
var sd = m_view.SortDescriptions ;
sd.Clear () ;
foreach (var desc in m_sourceView.SortDescriptions)
sd.Add (desc) ;
}
}
private void OnSourceCollectionChanged (object sender, NotifyCollectionChangedEventArgs e)
{
var toAdd = m_source.Except (m_collection) ;
var toRemove = m_collection.Except (m_source) ;
foreach (var obj in toAdd)
m_collection.Add (obj) ;
foreach (DependencyObject obj in toRemove)
{
var view = (FrameworkElement) obj.GetValue (ViewProperty) ;
var begintime = 1 ;
var sb = new Storyboard { BeginTime = TimeSpan.FromSeconds (begintime) } ;
sb.Completed += (s, ea) => m_collection.Remove (obj) ;
var fade = new DoubleAnimation (1, 0, new Duration (TimeSpan.FromMilliseconds (500))) ;
Storyboard.SetTarget (fade, view) ;
Storyboard.SetTargetProperty (fade, new PropertyPath (UIElement.OpacityProperty)) ;
sb.Children.Add (fade) ;
var size = new DoubleAnimation (view.ActualHeight, 0, new Duration (TimeSpan.FromMilliseconds (250))) ;
Storyboard.SetTarget (size, view) ;
Storyboard.SetTargetProperty (size, new PropertyPath (FrameworkElement.HeightProperty)) ;
sb.Children.Add (size) ;
size.BeginTime = fade.Duration.TimeSpan ;
sb.Begin () ;
}
}
private void OnViewCurrentChanged (object sender, EventArgs e)
{
m_sourceView.MoveCurrentTo (m_view.CurrentItem) ;
}
private void OnSourceCurrentChanged (object sender, EventArgs e)
{
m_view.MoveCurrentTo (m_sourceView.CurrentItem) ;
}
public ICollectionView View => m_view ;
}
}
And final words. First of all it works. Next - this approach don't require any changes in existing code, workarounds via Deleting property, etc, etc, etc. Especially when implemented as single custom control. You have ObservableCollection, add items, remove, do whatever you want, UI will always try to correctly reflect this changes.
Create two story boards for fade-in and fade-out and bind its value to the brush you've created for the OpacityMask of your ListBox
I've a custom SplitButton implementation in which contains a ComboBox with several ComboBoxItems bound to commands. I can bind to the Name, and Text properties of the command just fine but have no way of binding the ComboBoxItem's IsEnabled property to the result of a Command's CanExecute method because it is a method. Is there some syntax that I'm unaware of for binding to methods or is there some trickery that will help me to bind to CanExecute.
By the way, I've thought about using a custom ValueConverter except for that I realized that I probably wouldn't receive any updates when CanExecute is re-evaluated since it is not a property and since my commands are not business objects. It's looking to me that I might have to create a ViewModel for a command at this point to use only within my custom SplitButton control but that seems a little overboard to me.
You can put a button(if you dont have one in the controltemplate bound to the ICommand) inside ItemContainerStyle(ComboBoxItem style) and Bind the command to it
And add a Trigger to check the Button.IsEnabled and set that value to the ComboBoxItem. So here we used Button as a CommandSource just to get the IsEnabled from CanExeute. You can set the button's height and width to zero
<ControlTemplate....>
<Grid ...
<Button x:Name="dummyButton" Command="{Binding YourCommand}" ....
......
</Grid>
<ControlTemplate.Triggers>
<Trigger SourceName="dummyButton" Property="IsEnabled" Value="False">
<Setter Property="IsEnabled" Value="False"/>
</Trigger>
</ControlTemplate.Triggers>
Another solution by ViewModel. Below is how I used a ViewModel to solve my problem. And please note that the nifty NotifyPropertyChanged method is part of my base ViewModel class.
public class RoutedUICommandViewModel : ViewModel
{
private RoutedUICommand _command;
private IInputElement _target;
public string Name { get { return _command.Name; } }
public string Text { get { return _command.Text; } }
public bool CanExecute
{
get
{
return _command.CanExecute(null, _target);
}
}
public RoutedUICommand Command { get { return _command; } }
public RoutedUICommandViewModel(ReportCommand command, IInputElement target)
{
_command = command;
_target = target;
_command.CanExecuteChanged += _command_CanExecuteChanged;
}
private void _command_CanExecuteChanged(object sender, EventArgs e)
{
base.NotifyPropertyChanged(() => this.CanExecute);
}
}
I found this discussion on MSDN forums where Dr. WPF had recommended the use of an attached behavior to solve this exact problem. He gave the example below of how it would be used.
<Grid behaviors:CommandBehaviors.EnablingCommand="{x:Static commands:testcommand.test}">
. . .
</Grid>
Although this solution seems pretty nice I haven't been able to devote the time to understand exactly how this type of behavior would be implemented and what is involved. If anybody would like to elaborate please do otherwise I'll amend this answer with more details if I get the chance to explore this option.
The way I solved this problem in my code was to add an event handler on the ComboBox for the PreviewMouseDown event. Here's the handler:
private void comboBox_PreviewMouseDown(object sender, MouseButtonEventArgs e)
{
ViewModel vm = this.DataContext as ViewModel;
if (vm != null)
{
if (!vm.CanChangeSelection())
{
e.Handled = true;
vm.RespondToFailedAttemptChangeUnits();
}
}
}
This works great for me in the case that I only need to do this in one location. It might get a little tedius if I had many pages like this.
Also, though I follow the MVVM pattern, I'm not a purist - I consider this to be a good practical solution that follows the spirit of MVVM, if not the letter.
i am trying to make an item template where some of the field in my stack panel can be empty. When it's empty, I would like to set the visiblility to collapsed. I tried putting triggers but it doesn't seem to work and I am not very familiar with this part of WPF
Also, I would like to change the color of the background of this item when a specific value in my binding is true. Is it the same thing?
Thanks.
Using a ViewModel is one approach to solving this kind of problem.
The if your data was stored in an Item class you would make an ItemViewModel to wrap the Item for display in your items control. The ViewModel class would implement INotifyProperty changed in order to update the display and the setters would raise the PropertyChanged event passing the appropriate property name. You can also raise property changed events for as many interrelated changed fields as necessary.
Suppose you wanted Item.Description to display in a collapsed field when Description is empty. Your ViewModel properties could look like this
public string Description
{
get { return mItem.Description; }
set { mItem.Description = value; Notify("Description"); Notify("DescriptionVisibility"); }
}
public Visibility DescriptionVisibility
{
get { return string.IsNullOrEmpty(mItem.Description) ? Visibility.Visible : Visibility.Collapsed; }
}
In the XAML bind the text property to Description and the Visibility property to DescriptionVisibility.
If you want to hide an item if it's content is null, you have to redefine the ControlTemplate of its ListBoxItem (or ListViewItem or something else depending on which item container you're using) and use triggers that target the DataContext, like:
<DataTrigger Binding="{Binding}" Value="{x:Null}">
<Setter Property="Visibility" Value="Collapsed" />
</DataTrigger>
However, I'd suggest that you use the Filter delegate on your CollectionView to exclude your empty items from your view directly, to avoid collapsing unused items.
For example to exclude null objects, in your code behind, use:
CollectionViewSource.GetDefaultView(yourCollection).Filter = o => o != null;