The first thing I tried was using a TabControl. The problem with the TabControl was that each time you left a page the page would be unloaded. This is not the behaviour I wanted. The pages should stay in memory even when they aren’t visible. I searched for a solution and found this blog entry that described an extension to the tabcontrol called TabControlEx. I tried it out and indeed the items in the tab stayed in memory, but I couldn’t get it to work the way I wanted it.
Then I remembered the fluidkit library created by Pavan Podila. This library has a class called TransitionPresenter that derives from ItemsControl. You can make a slideshow with the items you add to this control. The items you add stay in memory when they aren’t shown on screen. Because I always try to write my programs with the MVVM pattern I looked for a viewmodel for this class. I found a blog entry by Jeremy Alles who extended the TransitionPresenter and created a viewmodel for his newly created class. The strange thing was that his new class loaded the visible pages and unloaded the invisible pages. I tried to change this behaviour in his class but couldn’t figure out what happened and gave up.
After my failed attempts with the TabControl and TabControlEx I decided to create my own viewmodel for the TransitionPresenter class. It turned out that the solution was relative easy. First I had to extend the TransitionPresenter class. I used attached properties to extend the behaviour of the TransitionPresenter class. I basically added one new attached dependency property. When you set the property called WorkSpaceNumber there will be a transition to the slide with this index number. If the index of the new slide is higher than the old one the transition animation will make a forward movement if the index is lower the transition will go backward. The static behaviour class you can see below:
public static class TransitionPresenterBehaviour
{
public static int GetWorkspaceNumber(DependencyObject obj)
{
return (int)obj.GetValue(WorkspaceNumberProperty);
}
public static void SetWorkspaceNumber(DependencyObject obj, int value)
{
obj.SetValue(WorkspaceNumberProperty, value);
}
// Using a DependencyProperty as the backing store for WorkspaceNumber. This enables animation, styling, binding, etc...
public static readonly DependencyProperty WorkspaceNumberProperty =
DependencyProperty.RegisterAttached("WorkspaceNumber", typeof(int),
typeof(TransitionPresenterBehaviour),
new UIPropertyMetadata(-1, OnIndexChanged));
private static void OnIndexChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
TransitionPresenter tp = (TransitionPresenter)d;
int newIndex = (int)e.NewValue;
int oldIndex = (int)e.OldValue;
FrameworkElement elNew = (FrameworkElement)tp.ItemContainerGenerator.ContainerFromItem(tp.Items[newIndex]);
FrameworkElement elOld = (oldIndex > -1) ?
(FrameworkElement)tp.ItemContainerGenerator.ContainerFromItem(tp.Items[oldIndex]) : elNew;
if (elNew != null)
{
TransitionPresenterBehaviour.SetForWard(tp,newIndex > oldIndex);
tp.ApplyTransition(elOld, elNew);
}
}
public static bool GetForWard(DependencyObject obj)
{
return (bool)obj.GetValue(ForWardProperty);
}
public static void SetForWard(DependencyObject obj, bool value)
{
obj.SetValue(ForWardProperty, value);
}
// Using a DependencyProperty as the backing store for ForWard. This enables animation, styling, binding, etc...
public static readonly DependencyProperty ForWardProperty =
DependencyProperty.RegisterAttached("ForWard", typeof(bool),
typeof(TransitionPresenterBehaviour),
new UIPropertyMetadata(false, ForwardChanged));
private static void ForwardChanged(DependencyObject d,
DependencyPropertyChangedEventArgs e)
{
TransitionPresenter tp = (TransitionPresenter)d;
forwardChanged(tp,(bool)e.OldValue, (bool)e.NewValue);
}
private static void forwardChanged(TransitionPresenter tp, bool oldRot, bool newRot)
{
if (oldRot == newRot)
return ;
if (tp.Transition is SlideTransition)
(tp.Transition as SlideTransition).Direction = (newRot) ? Direction.LeftToRight : Direction.RightToLeft;
if (tp.Transition is CubeTransition)
(tp.Transition as CubeTransition).Rotation = (newRot) ? Direction.LeftToRight : Direction.RightToLeft;
if (tp.Transition is FlipTransition)
(tp.Transition as FlipTransition).Rotation = (newRot) ? Direction.LeftToRight : Direction.RightToLeft;
if (tp.Transition is GenieTransition)
(tp.Transition as GenieTransition).EffectType = (newRot) ? GenieEffectType.IntoLamp : GenieEffectType.OutOfLamp;
}
}
The viewmodel for the TransitionPresenterEx class I called FluidViewModel. It is derived it from MainWindowViewModel, a class that Josh Smith describes in this article. I made a few changes to this class and it’s underlying classes. I described some of these changes in my previous blogs here and here. The source code for the FluidViewModel looks like this:
/// <summary>
/// A viewmodel for the fluidkit transitionpresenter class.
/// </summary>
public class FluidViewModel : MainWindowViewModel
{
/// <summary>
/// Initializes a new instance of the <see cref="FluidViewModel"/> class.
/// </summary>
public FluidViewModel(): base()
{
this._collectionView.CurrentChanged += new EventHandler(_collectionView_CurrentChanged);
this._collectionView.Refresh();
}
/// <summary>
/// Handles the CurrentChanged event of the _collectionView control.
/// </summary>
/// <param name="sender">The source of the event.</param>
/// <param name="e">The <see cref="System.EventArgs"/> instance containing the event data.</param>
void _collectionView_CurrentChanged(object sender, EventArgs e)
{
int to = this.Workspaces.IndexOf(this.ActiveWorkSpace);
// to update the Direction property
WorkspaceNumber=to;
}
private bool forward;
/// <summary>
/// Gets or sets the direction in which the pages slide.
/// </summary>
/// <value>The direction.</value>
public bool Forward
{
get
{
return this.forward;
}
set
{
this.forward = value;
RaisePropertyChanged(() => Forward);
}
}
private int workspaceNumber=-1, oldWorkspaceNr;
/// <summary>
/// Gets or sets the workspace number.
/// </summary>
/// <value>The workspace number.</value>
public int WorkspaceNumber
{
get
{
return this.workspaceNumber;
}
set
{
oldWorkspaceNr = this.workspaceNumber;
this.workspaceNumber = value;
// check the orientation of the change made by the user
this.Forward = (this.oldWorkspaceNr < this.workspaceNumber);
if (workspaceNumber != oldWorkspaceNr)
RaisePropertyChanged(() => WorkspaceNumber);
}
}
/// <summary>
/// Creates the commands.
/// </summary>
/// <returns>returns a list of commands</returns>
protected override List<CommandViewModel> CreateCommands()
{
List<CommandViewModel> commands = new List<CommandViewModel>();
foreach (WorkspaceViewModel ws in Workspaces)
commands.Add(new CommandViewModel(ws.DisplayName, ws.ShowCommand));
return commands;
}
}
As an example I created a small program with two images and two buttons. First a viewmodel for the main window of the program must be created. This is now very simple:
public class ImagesViewModel:FluidViewModel
{
Image1ViewModel image1;
public Image1ViewModel Image1
{
get { return image1; }
private set
{
image1 = value;
RaisePropertyChanged(() => Image1);
}
}
Image2ViewModel image2;
public Image2ViewModel Image2
{
get { return image2; }
private set
{
image2 = value;
RaisePropertyChanged(() => Image2);
}
}
public ImagesViewModel()
{
image1 = new Image1ViewModel(this);
image2 = new Image2ViewModel(this);
Workspaces.Add(image1);
Workspaces.Add(image2);
}
}
Connecting the ImagesViewModel to the TransitionPresenterEx control is now very easy. An example you can see in the xaml code below:
<DockPanel>
<ListBox DockPanel.Dock="Top" ItemsSource="{Binding Path=Commands}"
ItemTemplate="{StaticResource CommandsTemplate}">
</ListBox>
<ContentControl Name="workspacesContent"
Content="{Binding}"
ContentTemplate="{StaticResource WorkspacesTemplate}"/>
</DockPanel>
The datatemplate called WorkspacesTemplate changes the ImagesViewModel in a TransionPresenterEx object.
The most important datatemplates and styles that are used you can see below:
<DataTemplate DataType="{x:Type vm:Image1ViewModel}">
<vw:Image1Control/>
</DataTemplate>
<DataTemplate DataType="{x:Type vm:Image2ViewModel}">
<vw:Image2Control/>
</DataTemplate>
<DataTemplate x:Key="CommandsTemplate">
<Button Command="{Binding Path=Command}" Content="{Binding Path=DisplayName}">
</Button>
</DataTemplate>
<fl:SlideTransition x:Key="SlideTransition" />
<DataTemplate x:Key="WorkspacesTemplate" >
<fl:TransitionPresenter
RestDuration="0:0:3"
Transition="{StaticResource SlideTransition}"
ItemsSource="{Binding Path=Workspaces}"
vw:TransitionPresenterBehaviour.WorkspaceNumber="{Binding Path=WorkspaceNumber}">
</fl:TransitionPresenter>
</DataTemplate>
4 opmerkingen:
Robert,
This looks very cool ! I love the attached behavior and I think this is a great idea to use it here.
However I cannot build your sample, it seems that one of the project is missing. You could please check it out ?
Hi,
Thanks for the compliments.
I made a mistake and added an old library. The correct library is now included and everything should work now.
Robert
Robert,
I newly started learning WPF, MVVM and Entity Framework. Previously I read Beth Massi’s, Josh Smith’s, Vincent Sibal's and Karl Shifflett’s blogs. Finally I found your blog. Jason Dolinger’s video was great and I would like to thank you for sharing it.
In your recent fluidkit sample you put all the good things in your Wpf.MVVM project and I found them very usefull. Can you post simple examples that shows the usage of new ViewModels that you added to your library especially DialogViewModel?
Keep up with the good work.
Thanks,
I wrote the DialogViewModel a long time ago but I haven't had time yet to publish an example. I will try to write a blog post with examples as soon as possible now. So maybe next week you can expect a new article about my dialogviewmodel.
Een reactie posten