-
Notifications
You must be signed in to change notification settings - Fork 263
Converting from WinForms to WPF
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.
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);
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.
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); }
}
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.
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
.
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.
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 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));
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.
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.
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.
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;
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);
- ATF and WPF Overview: Overview of what ATF offers for WPF.
- WPF Application: Discuss how WPF applications differ from WinForms ones in basic application structure.
- Parallels in WPF and WinForms Support: Discusses features WPF offers that are similar to WinForms.
- WPF Application Support: Discussion of classes generally supporting applications.
- WPF Dialogs in ATF: WPF dialogs you can use.
- WPF Behaviors in ATF: ATF support for WPF behaviors.
- WPF Markup Extensions in ATF: ATF support for WPF Markup extensions.
- WPF ViewModels in ATF: Discuss WPF ViewModel support in ATF.
- Converting from WinForms to WPF: Tells how to convert a WinForms ATF-based application to WPF.
- Home
- Getting Started
- Features & Benefits
- Requirements & Dependencies
- Gallery
- Technology & Samples
- Adoption
- News
- Release Notes
- ATF Community
- Searching Documentation
- Using Documentation
- Videos
- Tutorials
- How To
- Programmer's Guide
- Reference
- Code Samples
- Documentation Files
© 2014-2015, Sony Computer Entertainment America LLC