Skip to content

Converting from WinForms to WPF

Gary edited this page Mar 10, 2015 · 2 revisions

The WinForms sample application ATF Simple DOM Editor Sample was converted to WPF to produce the ATF Simple DOM Editor WPF Sample, so comparing these two samples shows how to make this conversion for an ATF application. Diffing files in the two projects helps highlight the differences—and similarities.

These two applications are discussed in detail in Simple DOM Editor Programming Discussion and Simple DOM Editor WPF Programming Discussion.

This section compares the similarities and contrasts the differences.

Keep in mind that these sample applications both display a sequence of events in a list. This "event" is simply data and is distinct from a "programming event". This topic uses the word in both ways. The meaning of the word "event" here should be clear from the context.

Table of Contents

Basic Application Structure

WinForms and WPF applications are structured very differently. The WinForms Application and WPF Application topics discuss the basic structure of these applications in ATF.

Because there is little overlap in how these two kinds of applications are put together, build a WPF application by starting with the basic App.xaml and App.xaml.cs files discussed in App XAML.

MEF is used similarly in WinForms and WPF, so the main common element in setting up the application is the MEF TypeCatalog. This is moved from the Main() function in WinForms to the App.GetCatalog() method in App.xaml.cs in WPF, as shown in App XAML.

In addition, the WinForms sample added a AdapterCreator to enable metadata driven property editing for events and resources in its Main() function. The WPF version moves this to its SchemaLoader component's OnSchemaSetLoaded() method:

// Enable metadata driven property editing for events and resources
var creator = new AdapterCreator<CustomTypeDescriptorNodeAdapter>();
Schema.eventType.Type.AddAdapterCreator(creator);
Schema.resourceType.Type.AddAdapterCreator(creator);

Controls

WinForms and WPF create controls completely differently. WinForms constructs individual control instances; WPF can define them in XAML files.

For example, the SimpleDOMEditor sample's EventSequenceContext class's constructor creates and initializes a ListView instance in which it lists event sequences, as noted in EventSequenceContext Class:

m_listView = new ListView();
m_listView.SmallImageList = ResourceUtil.GetImageList16();
m_listView.AllowDrop = true;
m_listView.MultiSelect = true;
m_listView.AllowColumnReorder = true;
m_listView.LabelEdit = true;
m_listView.Dock = DockStyle.Fill;

By contrast, the SimpleDOMEditorWPF sample displays event sequences in a EventSequenceView control, which is defined in EventSequenceView.xaml, as seen in EventSequenceView Control:

<UserControl x:Class="SimpleDomEditorWpfSample.EventSequenceView"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             ...>
	...
    <Grid>
        <ListView ...>
            <ListView.View>
                <GridView AllowsColumnReorder="False">
                    <GridViewColumn Header="Name">
					...
                    <GridViewColumn Header="Duration">
					...

In the WPF sample, the EventSequenceContext constructor simply creates a new EventSequenceView:

m_view = new EventSequenceView();

The ResourceListEditor component has a similar change in the two samples. The WinForms version constructs a ListView as well as a ListViewAdapter. The WPF version has a ResourceListView.xaml file that defines a ResourceListView control that is constructed in ResourceListEditor.

You can define a control in a XAML file for every control the WinForm application uses.

Control Data Binding

Populating controls with data also differs in WinForms and WPF.

In WinForms, you can use a ListViewAdapter to adapt data to a ListView control. ListViewAdapter adapts a System.Windows.Forms.ListView control to an IListView. SimpleDOMEditor's EventSequenceContext class's OnNodeSet() method creates a ListViewAdapter:

protected override void OnNodeSet()
{
	...
	m_listViewAdapter = new ListViewAdapter(m_listView);
	m_listViewAdapter.AllowSorting = true;
	m_listViewAdapter.ListView = this;

In WPF, you can set up a data binding between the control and a data source. As well as defining the EventSequenceView control, the EventSequenceView.xaml file defines a data binding for its ListView:

<ListView x:Name="m_listView" ItemsSource="{Binding Events}" 
	SelectedItem="{Binding BindableSelection, Converter={StaticResource SelectionConverter}}">

For a description of this data binding, see Data Binding.

In general, you can replace data population by ATF adapters with WPF data binding, as in this example.

Because data binding is handled this way in WPF, the EventSequenceContext class is simpler in WPF than in WinForms. EventSequenceContext doesn't have to implement either IListView or IItemView, because these handle a ListView and items in it. (EventSequenceContext does implement IObservableContext and IInstancingContext identically to the WinForms sample.) Nor does EventSequenceContext need to construct a ListView or ListViewAdapter, and hence it doesn't need properties to access their instances. Instead it has a View property for its EventSequenceView instance. The WPF version also adds an Events property:

/// <summary>
/// Converts Items to Event objects for easy data binding</summary>
public IEnumerable<Event> Events
{
	get
	{
		foreach (object item in Items)
		{
			yield return item.As<Event>();
		}
	}
}

The WPF version of EventSequenceContext also has a BindableSelection property for two way data binding on selections:

/// <summary>
/// Exposes the Selection for two way data binding. Needed because the ISelectionContext.Selection
/// property is read-only.</summary>
public IEnumerable<object> BindableSelection
{
	get { return this.As<ISelectionContext>().Selection; }
	set { this.As<ISelectionContext>().SetRange(value); }
}

Event Handling

You need to handle some events differently in WPF than in WinForms.

For example, suppose the user changes a property of the selected event in the event sequence control. EventSequenceContext implements the WPF interface INotifyPropertyChanged to notify clients when a property value has changed and contains the PropertyChanged event. The PropertyChanged event is raised by all the DOM change event handlers.

The resource list also needs to be updated each time a new event is selected to display its resources. This updating is handled in the ResourceListEditor component in both samples, and in both, the ResourceListEditor constructor subscribes to the ActiveContextChanged event:

m_contextRegistry.ActiveContextChanged += contextRegistry_ActiveContextChanged;
...

private void contextRegistry_ActiveContextChanged(object sender, EventArgs e)
{
	// make sure we're always tracking the most recently active EventSequenceContext
	EventSequenceContext context = m_contextRegistry.GetMostRecentContext<EventSequenceContext>();
	if (m_eventSequenceContext != context)
	{
		if (m_eventSequenceContext != null)
		{
			m_eventSequenceContext.SelectionChanged -= eventSequenceContext_SelectionChanged;
		}

		m_eventSequenceContext = context;

		if (m_eventSequenceContext != null)
		{
			// track the most recently active EventSequenceContext's selection to get the most recently
			//  selected event.
			m_eventSequenceContext.SelectionChanged += eventSequenceContext_SelectionChanged;
		}

		UpdateEvent();
	}
}
...
private void eventSequenceContext_SelectionChanged(object sender, EventArgs e)
{
	UpdateEvent();
}

In both samples, the result is that whenever a new event is selected in the event sequence, UpdateEvent() is called to update the resource list.

Here's UpdateEvent() for WinForms:

private void UpdateEvent()
{
	Event nextEvent = null;
	if (m_eventSequenceContext != null)
		nextEvent = m_eventSequenceContext.Selection.GetLastSelected<Event>();

	if (m_event != nextEvent)
	{
		// remove last event's editing context in case it was activated
		if (m_event != null)
			m_contextRegistry.RemoveContext(m_event.Cast<EventContext>());

		m_event = nextEvent;

		// get next event's editing context and bind to resources list view
		EventContext eventContext = null;
		if (nextEvent != null)
			eventContext = nextEvent.Cast<EventContext>();

		m_resourcesListViewAdapter.ListView = eventContext;
	}
}

And here's the WPF version:

private void UpdateEvent()
{
	Event nextEvent = null;
	if (m_eventSequenceContext != null)
		nextEvent = m_eventSequenceContext.Selection.GetLastSelected<Event>();

	if (m_event != nextEvent)
	{
		// remove last event's editing context in case it was activated
		if (m_event != null)
			m_contextRegistry.RemoveContext(m_event.Cast<ResourceListContext>());

		m_event = nextEvent;

		// get next event's editing context and bind to resources list view
		ResourceListContext eventContext = null;
		if (nextEvent != null)
		{
			eventContext = nextEvent.Cast<ResourceListContext>();
			eventContext.View = m_resourceListView;
		}

		m_resourceListView.DataContext = eventContext;
	}
}

In the WPF sample, ResourceListContext corresponds to EventContext in the WinForms sample. Both provide an editing context for the resource list. So the real difference in UpdateEvent() in the two samples is only at the end.

The WinForms ending is:

if (nextEvent != null)
	eventContext = nextEvent.Cast<EventContext>();

m_resourcesListViewAdapter.ListView = eventContext;

It adapts nextEvent to EventContext and then sets the ListView property of the ListViewAdapter to the EventContext.

Here's the corresponding section in the WPF sample:

if (nextEvent != null)
{
	eventContext = nextEvent.Cast<ResourceListContext>();
	eventContext.View = m_resourceListView;
}

m_resourceListView.DataContext = eventContext;

The first part is essentially the same: it adapts nextEvent to ResourceListContext. It then updates the ResourceListContext's View property to the ResourceListView control. Finally, it sets the ResourceListView DataContext property to this ResourceListContext. The DataContext dependency property defines the ResourceListView control's Source. This means that the resource list is populated from the correct event's resources so that the data binding works properly. For more details on using DataContext in WPF data binding, see Data Binding.

Miscellaneous Control Changes

Not using WinForms controls means you have to make some other changes as well.

For instance, using WPF controls also means that some of the handlers that apply to WinForms control events are not needed. For instance, ResourceListEditor's IInitializable.Initialize() method constructs a ListViewAdapter for the ListView and defines an event handler for its LabelEdited event:

m_resourcesListViewAdapter = new ListViewAdapter(m_resourcesListView);
m_resourcesListViewAdapter.LabelEdited +=
	resourcesListViewAdapter_LabelEdited;

This is unnecessary in the WPF sample, because it doesn't use a ListViewAdapter.

For another example, consider Editor.cs's implementations of IDocumentClient in the two samples. One difference is where the Open() method stores the EventSequenceDocument object. In WinForms, Open() does this:

context.ListView.Tag = document;

whereas in WPF:

context.Document = document;

This has to change, because WPF doesn't use a ListView. In both cases, context is a EventSequenceContext. The WPF version simply defines a Document property for the EventSequenceDocument.

ATF DOM

This is one area that needs almost no change, because the DOM is almost entirely independent of WinForms and WPF. For this reason, the eventSequence.xsd files are identical and the Schema.cs files differ only in their namespaces.

Similarly, the DOM adapters are essentially the same.

Handlers for DOM related events are much the same in both samples.

Document Handling

This changes little for WinForms and WPF. The EventSequenceDocument implementation is very similar. ATF has only one IDocumentClient and its implementation differs little in the samples.

ISearchableContext is not implemented in the WPF sample, because there is no node search.

Palette Handling

Palette handling differs somewhat, because there are WinForms and WPF versions of IPaletteService and the PaletteService component. The WPF IPaletteService actually derives from the WinForms version, adding a Convert() method to convert from palette items to actual items.

There are also two versions of NodeTypePaletteItem with different constructors. The WPF version also uses properties rather than fields. NodeTypePaletteItem differences are the main source of differences between the PaletteClient implementations.

The IPaletteClient methods behave a little differently. Here is IPaletteClient.GetInfo() for WinForms:

void IPaletteClient.GetInfo(object item, ItemInfo info)
{
	DomNodeType nodeType = (DomNodeType)item;
	NodeTypePaletteItem paletteItem = nodeType.GetTag<NodeTypePaletteItem>();
	if (paletteItem != null)
	{
		info.Label = paletteItem.Name;
		info.Description = paletteItem.Description;
		info.ImageIndex = info.GetImageList().Images.IndexOfKey(paletteItem.ImageName);
	}
}

And for WPF:

void IPaletteClient.GetInfo(object item, ItemInfo info)
{
	NodeTypePaletteItem paletteItem = item.As<NodeTypePaletteItem>();
	if (paletteItem != null)
	{
		info.Label = paletteItem.Name;
		info.Description = paletteItem.Description;
	}
}

Note that the WPF version is simpler in that the ItemInfo can be adapted directly to a NodeTypePaletteItem. (The WPF version does not implement setting an image for the palette item.)

The difference in NodeTypePaletteItem also affects the SchemaLoader class, because it creates NodeTypePaletteItem objects for the palette and places them in the tags. For instance, here is a tag being set in the WinForms sample:

Schema.eventType.Type.SetTag(
	new NodeTypePaletteItem(
		Schema.eventType.Type,
		(string)Schema.eventType.nameAttribute.DefaultValue,
		"Event in a sequence".Localize(),
		Resources.EventImage));

Here is setting the same tag in the WPF sample:

Schema.eventType.Type.SetTag(
	new NodeTypePaletteItem(
		Schema.eventType.Type,
		"Event".Localize(),
		"Event in a sequence".Localize(),
		"Events".Localize(),
		Resources.EventImage));

Differences in Class and Component Implementations

ATF implements different versions of some classes and components in WinForms and WPF. Sometimes this change is transparent, but not always.

Upfront, the WPF Editor.cs makes clear which version it's using, along with a few other WPF versions:

using IControlHostClient = Sce.Atf.Wpf.Applications.IControlHostClient;
using IControlHostService = Sce.Atf.Wpf.Applications.IControlHostService;
using ControlInfo = Sce.Atf.Wpf.Applications.ControlInfo;

For example, IControlHostService and thus the ControlHostService component are not the same for WinForms and WPF. The IControlHostService.RegisterControl() differs, and WinForms calls it this way:

m_controlHostService.RegisterControl(context.ListView, controlInfo, this);

But WPF calls it this way:

m_controlHostService.RegisterControl(context.View,
	fileName, 
	"Event sequence document", 
	StandardControlGroup.Center, 
	Path.Combine(filePath, fileName), 
	this);

In addition, the common application shell components CommandService, StatusService, and SettingsService have WinForms and WPF versions.

ControlInfo, which holds information about controls hosted by IControlHostService, also has WinForms and WPF versions. The ControlInfo constructors for WinForms and WPF differ, so the Editor.cs in each version creates their instances differently. In WinForms:

ControlInfo controlInfo = new ControlInfo(fileName, filePath, StandardControlGroup.Center);

And in WPF:

ControlInfo controlInfo = new ControlInfo(Path.Combine(filePath, fileName),
	StandardControlGroup.Center,
	new DockContent(null, null), this);

DockContent is a WPF class that represents content to be docked in the docking framework.

Additional WPF Classes

WPF provides classes you can use instead of WinForms ones. For example, it offers a set of property editing classes, such as SliderEditor with a slider control and StandardValuesEditor for enumerations.

As a result, SchemaLoader sets up attribute editors a little differently in one case. Here's the WinForms version:

new AttributePropertyDescriptor(
	"Primitive Kind".Localize(),
	Schema.geometryResourceType.primitiveTypeAttribute,
	null,
	"Kind of primitives in geometry".Localize(),
	false,
	new EnumUITypeEditor(primitiveKinds),
	new EnumTypeConverter(primitiveKinds)),

And for WPF:

new AttributePropertyDescriptor(
	"Primitive Kind".Localize(),
	Schema.geometryResourceType.primitiveTypeAttribute,
	null,
	"Kind of primitives in geometry".Localize(),
	false,
	StandardValuesEditor.Instance,
	null,
	new Attribute[] { new StandardValuesAttribute(primitiveKinds)})

The WPF version uses a different form of the AttributePropertyDescriptor constructor, uses the property editor StandardValuesEditor instead of EnumUITypeEditor, and uses no converter. It doesn't need to construct the StandardValuesEditor either, using a static instance of it from the Instance property.

Behaviors

The same behaviors are defined for both WPF controls in their XAML for their ListView:

<ListView x:Name="m_listView" ItemsSource="{Binding Resources}"
		SelectedItem="{Binding BindableSelection, Converter={StaticResource SelectionConverter}}">
	<i:Interaction.Behaviors>
		<behaviors:InstancingDropTargetBehavior/>
		<behaviors:ContextMenuBehavior/>
	</i:Interaction.Behaviors>

InstancingDropTargetBehavior and ContextMenuBehavior are WPF behavior classes defined in ATF. This eliminates the need for the application to provide additional code for context menus or for drag and drop.

For instance, the WinForms ResourceListContext component subscribes to mouse events on its ListView to handle drag and drop in its IInitializable.Initialize():

m_resourcesListView.DragOver += resourcesListView_DragOver;
m_resourcesListView.DragDrop += resourcesListView_DragDrop;
m_resourcesListView.MouseUp += resourcesListView_MouseUp;

The WPF equivalent is the behavior definition in the XAML shown above.

For more information on how behaviors are used in the WPF sample, see Behaviors.

Namespaces Used

You need to add WPF the namespaces you use to files where they are referenced. For example, the WPF version of Editor.cs adds these namespaces:

using Sce.Atf.Wpf.Applications;
using Sce.Atf.Wpf.Docking;

Miscellaneous Changes

In WPF, Editor.cs has this section in its IInitializable.Initialize() method to convert an image format for WPF:

// Set the application icon. We need to convert the resource from
// System.Drawing.Image to System.Windows.Media.ImageSource.
System.Drawing.Image atfIcon = Sce.Atf.ResourceUtil.GetImage(Sce.Atf.Resources.AtfIconImage);
System.Windows.Application.Current.MainWindow.Icon = Sce.Atf.Wpf.ResourceUtil.ConvertWinFormsImage(atfIcon);

Topics in this Section

Clone this wiki locally