WPF and Legacy Code Henry Sowizral Architect Microsoft

  • Slides: 61
Download presentation
WPF and Legacy Code Henry Sowizral Architect, Microsoft Expression Studio Ivo Manolov Test Manager,

WPF and Legacy Code Henry Sowizral Architect, Microsoft Expression Studio Ivo Manolov Test Manager, WPF

Objectives and Takeaways Objectives Learn how to augment a native application with a WPF

Objectives and Takeaways Objectives Learn how to augment a native application with a WPF interface Learn to ensure quality by reducing errors during migration Answer such questions as: “Can you move an MFC (Win 32) based application to WPF? ” “Does it make more sense to rewrite than migrate? ” “How do you design an native to managed code interop solution? ” Takeaways User Experience matters—it adds value WPF makes adding a rich UX easier Adding a rich UX does not require a full rewrite

Overview Introduction to WPF and Win 32 interop Why WPF? Types of WPF-Win 32

Overview Introduction to WPF and Win 32 interop Why WPF? Types of WPF-Win 32 Interop WPF and legacy code (deep dive) Case study: Expression™ Design MFC to WPF in three easy steps The hard work: converting the UI Summary

Introduction to WPF and Win 32 Interop Ivo Manolov

Introduction to WPF and Win 32 Interop Ivo Manolov

Why WPF? Because it’s 2008 User experience matters Usability is a competitive advantage Usability

Why WPF? Because it’s 2008 User experience matters Usability is a competitive advantage Usability is productivity Rich integrated experiences require rich, integrated platforms WPF supports proper SW design The use of MVC these days is crucial You get to do WYSIWYG UI design WPF is paying a lot of the “taxes” for you WPF TCO is significantly lower than the equivalent Win 32 / DHTML / Direct. X TCO. WPF is Microsoft’s premier desktop application development framework

WPF vs Win 32 WPF features: Control composition Control styling and templating Vector UI

WPF vs Win 32 WPF features: Control composition Control styling and templating Vector UI Advanced text 2 D / 3 D / Imaging / Media / Animations

WPF Supports MVC Natively DBs Presentation Layer (*. XAML) Business Logic Web Services (*.

WPF Supports MVC Natively DBs Presentation Layer (*. XAML) Business Logic Web Services (*. CS / *. CPP) COM / Win 32 /. NET components 7

WPF-Native Interoperation Traditional Interop (since. NET 1. 0) Call into flat API DLLS (e.

WPF-Native Interoperation Traditional Interop (since. NET 1. 0) Call into flat API DLLS (e. g. kernel 32. dll) COM Interop Hosting scenarios WPF hosting an HWND (Hwnd. Host) WPF hosting Win. Forms (Windows. Form. Host) HWND hosting WPF (Hwnd. Source) Win. Forms hosting WPF (Element. Host)

WPF Hosting Win. Forms Controls Three simple steps: 1. Add a <Windows. Forms. Host…/>

WPF Hosting Win. Forms Controls Three simple steps: 1. Add a <Windows. Forms. Host…/> to your XAML 2. Add a reference to the namespace of your Win. Forms control and instantiate the control in XAML 3. Add event handlers to propagate Win. Forms control events to the WPF app. <Window. . . xmlns: wf="clr-namespace: System. Windows. Forms; assembly=System. Windows. Forms" > . . . <Windows. Forms. Host> <wf: Data. Grid. View x: Name="data. Grid. View" Location="0, 0" Column. Headers. Visible="True" Selection. Mode="Full. Row. Select" Multi. Select="False" Selection. Changed="Data. Grid. View. On. Selection. Changed" /> </Windows. Forms. Host> . . . </Window>

WPF Hosting HWNDs

WPF Hosting HWNDs

WPF Hosting HWNDs (cont. ) class Win 32 List. Box. Host : Hwnd. Host,

WPF Hosting HWNDs (cont. ) class Win 32 List. Box. Host : Hwnd. Host, IKeyboard. Input. Sink { public int Add. Item(string item) {. . . } public void Delete. Item(int item. Index) {. . . }. . . public event Event. Handler Selection. Changed; protected virtual void On. Selection. Changed(Event. Args args) {. . . } bool IKeyboard. Input. Sink. Tab. Into(Traversal. Request request); bool IKeyboard. Input. Sink. Translate. Accelerator(ref MSG msg, Modifier. Keys mk); protected override Handle. Ref Build. Window. Core(Handle. Ref hwnd. Parent) {. . . } protected override void Destroy. Window. Core(Handle. Ref hwnd) {. . . } protected override Int. Ptr Wnd. Proc(Int. Ptr hwnd, int message, Int. Ptr w. Param, Int. Ptr l. Param, ref bool handled) {. . . } } <Window. . . xmlns: a="clr-namespace: Win 32 Control. In. Wpf. Window; assembly="> . . . <a: Win 32 List. Box. Host x: Name=“listbox“ Width=“ 100“ Height=“ 100“ Selection. Changed=“My. Selection. Changed. Handler”/> . . . </Window>

WPF Hosting HWNDs—Gotchas 1. Build. Window. Core is where you instantiate your HWND. You

WPF Hosting HWNDs—Gotchas 1. Build. Window. Core is where you instantiate your HWND. You typically need to instantiate it as a child of a dummy parent HWND. 2. Do not expose Win 32 -esque idioms to the users of your Hwnd. Host-derived class. 3. Be aware of airspace limitations. 4. Be aware of transform and opacity limitations (no transforms, 100% opacity only) • Implement custom layout / scaling logic to be able to scale up/down the UI. 5. Do not forget keyboard accessibility and accessibility in general.

WPF and Legacy Code (Deep Dive) Henry Sowizral

WPF and Legacy Code (Deep Dive) Henry Sowizral

Overview Case study: Expression™ Design Demo of Expression Design MFC to WPF in 3

Overview Case study: Expression™ Design Demo of Expression Design MFC to WPF in 3 Easy Steps User Interface Constituents Visual Components Focus (and event processing) Summary

Motivation Multiple products in Expression Studio Expression Blend—newly written (WPF / C#) Expression Design—legacy

Motivation Multiple products in Expression Studio Expression Blend—newly written (WPF / C#) Expression Design—legacy (MFC / ASM, C, C++) Consistent look and feel Establish the “Expression” brand

The Need Product perspective A consistent user experience across products Cutting edge UI that

The Need Product perspective A consistent user experience across products Cutting edge UI that inspires designers Development perspective Enable rapid development Resilience to UX specification changes Incremental update

The Transformation Desired

The Transformation Desired

Expression Design 10 year old C++ code base Structured to run on both Windows

Expression Design 10 year old C++ code base Structured to run on both Windows and Mac 10 year old user experience (look and feel) Poor separation of data model and user interface

Possible Approaches Rewrite using MFC — costly Use owner-draw to “recolor” the UI —

Possible Approaches Rewrite using MFC — costly Use owner-draw to “recolor” the UI — cosmetic Use WPF — makes sense

Separate The Core From The Interface

Separate The Core From The Interface

New Architecture

New Architecture

Apply The Architecture

Apply The Architecture

The Result (of The Face Lift)

The Result (of The Face Lift)

Converting an MFC Application to Use a WPF User Interface In three easy steps…

Converting an MFC Application to Use a WPF User Interface In three easy steps…

Modify The MFC Application Split the MFC application in two Turn the application into

Modify The MFC Application Split the MFC application in two Turn the application into a DLL Construct a stub “main” to call the new DLL Clean up memory allocation, if needed Remove all instances of local (custom) “new”s Ensure all thread local storage “operates well” in a delay loaded DLL

Create A WPF Application And Integrate The MFC Code Construct a new “main” Calls

Create A WPF Application And Integrate The MFC Code Construct a new “main” Calls the new MFC dll Creates a WPF window for hosting MFC code Subclass Hwnd. Host, specifically Build. Window. Core to Take the WPF Hwnd that parents the MFC app Return the child Hwnd created by the MFC app Destroy. Window. Core to Destroy the child Hwnd

Subclassing Hwnd. Host public class MFCHwnd. Host : Hwnd. Host { protected override Handle.

Subclassing Hwnd. Host public class MFCHwnd. Host : Hwnd. Host { protected override Handle. Ref Build. Window. Core(Handle. Ref hwnd. Parent) { Int. Ptr child. Window = MFCHost. Create. Child. Window(hwnd. Parent. Handle); Handle. Ref child. Window. Handle. Ref = new Handle. Ref(this, child. Window); return child. Window. Handle. Ref; } protected override void Destroy. Window. Core(Handle. Ref hwnd) { // TODO. } }

Create the WPF UI And Connect It To The MFC Code Define the new

Create the WPF UI And Connect It To The MFC Code Define the new WPF-based user interface Integrate the new UI with the MFC DLL Use the C++ compiler’s /CLR option to create adapter code between MFC and WPF Or use P/Invoke to call the MFC DLL Construct static entry points in the MFC application for use by the new UI Write the UI code to use the newly constructed entry points via. NET’s native calling capability

Mimicking Windows — to match MFC’s expectations

Mimicking Windows — to match MFC’s expectations

What Breaks?

What Breaks?

What Breaks MFC in WPF? MFC expects a specific windows hierarchy Assumption: the parent

What Breaks MFC in WPF? MFC expects a specific windows hierarchy Assumption: the parent of an MFC’s root window should be the display Hosting within WPF breaks that expectation Just hosting MFC in WPF is not enough MDI sometimes optimizes out message Need to regeneration or relaying messages

Making MFC MDI Work Override MFC event handlers to Propagate the minimize/maximize/close events On.

Making MFC MDI Work Override MFC event handlers to Propagate the minimize/maximize/close events On. Window. Pos. Changed On. Sys. Command—but only if command ID is SC_CLOSE On. Close Propagate non-client area refresh (MDI frames) On. MDIActivate—emit WM_NCACTIVATE On. Size—emit WM_NCACTIVATE But only when WS_SYSMENU is cleared and restoring or minimizing Lastly, force WS_SYSMENU to true

void CChild. Frame: : On. Sys. Command(UINT n. ID, LPARAM l. Param) { CMDIChild.

void CChild. Frame: : On. Sys. Command(UINT n. ID, LPARAM l. Param) { CMDIChild. Wnd: : On. Sys. Command(n. ID, l. Param); if (g. Register. MDIState. Changed. Callback != NULL && n. ID == SC_MINIMIZE || n. ID == SC_MAXIMIZE || n. ID == SC_RESTORE) { g. Register. MDIState. Changed. Callback(); } }

But Consider Document Tabs

But Consider Document Tabs

A New User Interface Defining and Integrating WPF and C++

A New User Interface Defining and Integrating WPF and C++

User Interface Constituents Visual Components Application window(s) Control Panels (Controls) Dialogs Focus (and event

User Interface Constituents Visual Components Application window(s) Control Panels (Controls) Dialogs Focus (and event processing)

Visual Components — converting to the new look and feel

Visual Components — converting to the new look and feel

Controls Before and After

Controls Before and After

An MFC Control

An MFC Control

Information Flow Within MFC

Information Flow Within MFC

MFC Control Source Code Paint. Palette: : Handle. Control. Message(int control. ID, Message& message)

MFC Control Source Code Paint. Palette: : Handle. Control. Message(int control. ID, Message& message) { switch (control. ID) { //. . . case STROKE_BUTTON_PRESSED: Swap. To. Color. Control(STROKE_CONTROL); Control. Properties. Set. Stroke. Type(SOLID); Control. Properties. Set. Stroke. Color(current. Color); Control. Properties. Invalidate. Stroke. Color(); Set. Color. Control. Focus(STROKE_FOCUS); Common. Properties. Invalidate. Stroke. Type(); Commit. Property. Changes(); break; //. . . } }

Model View Controller

Model View Controller

Separating Model and View Separate Model (underlying data) View (presentation) and Controller (operations) Identify

Separating Model and View Separate Model (underlying data) View (presentation) and Controller (operations) Identify the model (data) manipulation code Encapsulate it as a method Move it to a supporting class/file Replace it with a call to the encapsulated method

Identify Model Manipulation Paint. Palette: : Handle. Control. Message(int control. ID, Message& message) {

Identify Model Manipulation Paint. Palette: : Handle. Control. Message(int control. ID, Message& message) { switch (control. ID) { //. . . case STROKE_BUTTON_PRESSED: Swap. To. Color. Control(STROKE_CONTROL); Control. Properties. Set. Stroke. Type(SOLID); Control. Properties. Set. Stroke. Color(current. Color); Control. Properties. Invalidate. Stroke. Color(); Set. Color. Control. Focus(STROKE_FOCUS); Common. Properties. Invalidate. Stroke. Type(); Commit. Property. Changes(); break; //. . . } }

Extract Model Manipulation Code void Paint. Palette. Linkage: : Set. Solid. Stroke_BB 1(new. Color)

Extract Model Manipulation Code void Paint. Palette. Linkage: : Set. Solid. Stroke_BB 1(new. Color) { Paint. Attribute. Set. Stroke. Type(SOLID); Paint. Attribute. Set. Stroke. Color(new. Color); Common. Attributes. Invalidate. Stroke. Color(); } void Paint. Palette. Linkage: : Set. Solid. Stroke_BB 2 () { Common. Attributes. Invalidate. Stroke. Type(); }

Call Extracted Code Paint. Palette: : Handle. Control. Message(int control. ID, Message& message) {

Call Extracted Code Paint. Palette: : Handle. Control. Message(int control. ID, Message& message) { switch (control. ID) { //. . . case SOLID_STROKE_BUTTON: Swap. To. Color. Control(STROKE_CONTROL); Paint. Palette. Linkage: : Set. Solid. Stroke_BB 1(current. Color); Set. Color. Control. Focus(STROKE_FOCUS); Paint. Palette. Linkage: : Set. Solid. Stroke_BB 2(); Commit. Property. Changes(); break; //. . . } }

Information Flow In The Interim

Information Flow In The Interim

Xaml (Defining A Button). . . <Button Command="{Binding Set. Solid. Stroke. Command}" />. .

Xaml (Defining A Button). . . <Button Command="{Binding Set. Solid. Stroke. Command}" />. . .

Backing Code public ICommand Set. Solid. Stroke. Command { get { return new Command.

Backing Code public ICommand Set. Solid. Stroke. Command { get { return new Command. Proxy(this. Set. Solid. Stroke. Type); } } public void Set. Solid. Stroke. Type() { this. Paint. Palette. Shim. Stroke. Type = Shims. Stroke. Type. Solid; this. Paint. Palette. Shim. Commit. And. Update(); }

Shim void Set. Solid. Stroke. Type() { uint 32 ambient. Color = Paint. Attribute.

Shim void Set. Solid. Stroke. Type() { uint 32 ambient. Color = Paint. Attribute. Get. Ambient. Stroke. Color(); Paint. Palette. Linkage: : Set. Solid. Stroke_BB 1(ambient. Color); Paint. Palette. Linkage: : Set. Solid. Stroke_BB 2(); } … public delegate void Update. Event. Handler(); public Update. Event. Handler^ update. Paint. Palette;

The Final Flow of Control

The Final Flow of Control

Hydra-Headed UI

Hydra-Headed UI

Controller — Where’s the controller

Controller — Where’s the controller

Processing Input Need explicit control over focus Which control Which palette When Handling focus

Processing Input Need explicit control over focus Which control Which palette When Handling focus MFC and WPF controls have a default focus Not always what the application designer desires Specific controls grab focus at too granular a level Who owns the message pump?

Can’t Serve Two Masters Choose either MFC or WPF To own the message pump

Can’t Serve Two Masters Choose either MFC or WPF To own the message pump To redirect (or intercept) messages as appropriate Expression Design’s architecture allowed one choice

WPF As Message Traffic Cop Hook into WPF dispatcher (message pump) Component. Dispatcher. Thread.

WPF As Message Traffic Cop Hook into WPF dispatcher (message pump) Component. Dispatcher. Thread. Filter. Message += new Thread. Message. Event. Handler(…); Why? So you can Redirect keyboard or mouse operations Selectively apply accelerator keys across the application Selectively allow MFC to “preview” messages (events) Control where focus should move

A Sample Message Filter Component. Dispatcher. Thread. Filter. Message += new Thread. Message. Event.

A Sample Message Filter Component. Dispatcher. Thread. Filter. Message += new Thread. Message. Event. Handler(Message. Filter); private void Message. Filter(ref System. Windows. Interop. MSG msg, ref bool handled) { if ((msg. message == Native. Constants. WM_KEYDOWN) && Should. Enable. Shortcuts() && !Is. Editing. Text()) { this. Allow. Character. Short. Cut. Processing(); } } if (msg. message == Native. Constants. WM_MOUSEWHEEL || (msg. message >= Native. Constants. WM_KEYFIRST && msg. message <= Native. Constants. WM_KEYLAST)) { if (Should. Allow. Native. Message. Preview()) { handled = Native. Code. Preview. Messages(msg. hwnd, msg. message, msg. pt_x, msg. pt_y, msg. time, msg. w. Param, msg. l. Param); } }

Dialog Creation Hand convert dialogs into Xaml How? Use Expression Blend™

Dialog Creation Hand convert dialogs into Xaml How? Use Expression Blend™

Summary You can Retaining an legacy applications core value And add a new WPF-based

Summary You can Retaining an legacy applications core value And add a new WPF-based user experience A new WPF interface can add value Improve look, feel, and end-user experience Enable flexibility in UI modification

Key Takeaways User Experience matters! WPF Makes high-end user experience practical Makes “Traditional” UX

Key Takeaways User Experience matters! WPF Makes high-end user experience practical Makes “Traditional” UX quick and easy to build Enables an ecosystem: Tools, 3 rd-party, and developer and designer communities Enables a client continuum: ASP. NET -> Ajax > Silverlight -> WPF Adding a rich UI does not require a rewrite

How do I get started? • On the web: • Books: • Get in

How do I get started? • On the web: • Books: • Get in touch: – http: //Windows. Client. net – MSDN: search for “WPF” (link) – “Windows Presentation Foundation Unleashed (WPF)” by Adam Nathan – “Applications = Code + Markup: A Guide to the Microsoft Windows Presentation Foundation” by Charles Petzold – “Programming Windows Presentation Foundation” by Chris Sells and Ian Griffiths – “Essential Windows Presentation Foundation” by Chris Anderson – henryso@microsoft. com – ivom@microsoft. com 61