Thursday, November 3, 2011

Flash in WPF Application the MVVM way (the hard part)

The Flash ActiveX control can not be added directly to the XAML file. Okay, the solution is well-known - just like with the Windows Forms control, you can use WindowsFormsHost to, well, host it - that's what the host is for. Then we add some code to the code-behind to load the movie and play it, and everything works. Right? Sort of. What if I'm trying my best to do the things the MVVM way? My code-behind file is usually empty, and all the business logic happens in the ViewModel. My XAML file does not have any button1_click event handlers, but rather is linked to the ViewModel by binding and is notified when something changes by means of OnPropertyChanged. What to do? The hard part is to come up with something that can be placed into the XAML file. The easy part is to place that something into the XAML file and bind it to the ViewModel. I'll start with the hard part, and use Visual Studio 2010.

Let's assume that the user controls are in a separate project. So I create a new project using the "WPF User Control Library" template and call it WPFControlLibrary. Let's delete UserControl1.xaml so it doesn't get in the way. First I'll add references to the COM components I may need.

Now I add a User Control (not the User Control - WPF yet!) and call it FlashWrapper. I add the AxShockwaveFlash control to it and call it axShockwafeFlash. For now let's worry only about loading and playing a movie. That's all the code I'll need:

using System.Windows.Forms;

namespace WPFControlLibrary
{
public partial class FlashWrapper : UserControl
{
public FlashWrapper()
{
InitializeComponent();
}

public void LoadMovie(string movie)
{
axShockwaveFlash.Movie = movie;
}

public void Play()
{
axShockwaveFlash.Play();
}

public void Stop()
{
axShockwaveFlash.Stop();
}
}
}

Now is the time to add the User Control - WPF. I call it FlashWPF.xaml. The XAML file is where the WindowsFormsHost comes to play - here I will host my FlashWrapper. Don't forget to add a reference to WindowsFormsIntegration!

<UserControl x:Class="WPFControlLibrary.FlashWPF"
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"
xmlns:local="clr-namespace:WPFControlLibrary"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300">
<Grid >
<WindowsFormsHost >
<WindowsFormsHost.Child>
<local:FlashWrapper x:Name="FlashPlayer" />
</WindowsFormsHost.Child>
</WindowsFormsHost>
</Grid>
</UserControl>

And still not much of C# code yet.

using System.Windows.Controls;

namespace WPFControlLibrary
{
public partial class FlashWPF : UserControl
{
public FlashWPF()
{
InitializeComponent();
}

public void LoadMovie(string movie)
{
FlashPlayer.LoadMovie(movie);
}

public void Play(bool value)
{
if (value)
FlashPlayer.Play();
else
FlashPlayer.Stop();
}
}
}

Last, and the most complex and cryptic step is to add a custom control. This will be a link between the MVVM application and the WPF control which hosts the wrapper which wraps the ActiveX control.

This is the ViewModel.
That is bound to the WPF custom control.
That hosts the C# wrapper.
That wraps the ActiveX control.
That plays the movie.

I think I got carried away a bit.

Ok, let's add a Custom Control - WPF and call it FlashControl. That's how it looks if all was done correctly:

public class FlashControl : Control
{
static FlashControl()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(FlashControl), new FrameworkPropertyMetadata(typeof(FlashControl)));
}
}

I have to modify it to expose three DependencyProperties: Movie, Play, and finally itself so it can be created in the MVVM application. And this is the end result:

public class FlashControl : Control
{
static FlashControl()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(FlashControl), new FrameworkPropertyMetadata(typeof(FlashControl)));
}

public FlashControl()
{
FlashPlayer = new FlashWPF();
}

//*****************************************************************************************

//Movie property definition

public static readonly DependencyProperty MovieProperty = DependencyProperty.RegisterAttached("Movie", typeof(string), typeof(FlashControl), new PropertyMetadata(MovieChanged));

private static void MovieChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
(d as FlashControl).Movie = (string)e.NewValue;
}

//Play movie property definition
public static readonly DependencyProperty PlayProperty = DependencyProperty.RegisterAttached("Play", typeof(bool), typeof(FlashControl), new PropertyMetadata(PlayChanged));

private static void PlayChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
(d as FlashControl).Play = (bool)e.NewValue;
}

//Flash player WindowFormHost
public static readonly DependencyProperty FlashPlayerProperty = DependencyProperty.RegisterAttached("FlashPlayer", typeof(FlashWPF), typeof(FlashControl), new PropertyMetadata(FlashPlayerChanged));

private static void FlashPlayerChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
(d as FlashControl).FlashPlayer = (FlashWPF)e.NewValue;
}

//*****************************************************************************************

public string Movie
{
get { return (string)this.GetValue(MovieProperty); }
set
{
this.SetValue(MovieProperty, value);
FlashPlayer.LoadMovie(value);
}
}

public bool Play
{
get { return (bool)this.GetValue(PlayProperty); }
set
{
this.SetValue(PlayProperty, value);
FlashPlayer.Play(value);
}
}

public FlashWPF FlashPlayer
{
get { return (FlashWPF)this.GetValue(FlashPlayerProperty); }
set { this.SetValue(FlashPlayerProperty, value); }
}
}

And the XAML file (which is for some reason called Generic.xaml and also when I tried to rename it I started getting errors, so I decided to leave the name alone) - I modified it slightly, but that's a matter of personal preference:

<ResourceDictionary
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:WPFControlLibrary">
<Style TargetType="{x:Type local:FlashControl}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type local:FlashControl}">
<Grid HorizontalAlignment="Stretch" VerticalAlignment="Stretch">
<ContentControl Content="{TemplateBinding FlashPlayer}" />
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ResourceDictionary>

And the hard part is over!

by . Also posted on my website

No comments: