Programming WPF 2nd Edition

openbj521

贡献于2014-09-04

字数:0 关键词: .NET开发

Programming WPF SECOND EDITION Chris Sells and Ian Griffiths Beijing • Cambridge • Farnham • Köln • Paris • Sebastopol • Taipei • Tokyo Programming WPF, Second Edition by Chris Sells and Ian Griffiths Copyright © 2007, 2005 O’Reilly Media, Inc. All rights reserved. Printed in the United States of America. Published by O’Reilly Media, Inc., 1005 Gravenstein Highway North, Sebastopol, CA 95472. O’Reilly books may be purchased for educational, business, or sales promotional use. Online editions are also available for most titles (safari.oreilly.com). For more information, contact our corporate/institutional sales department: (800) 998-9938 or corporate@oreilly.com. Editor: John Osborn Production Editor: Rachel Monaghan Copyeditor: Audrey Doyle Proofreader: Rachel Monaghan Indexer: John Bickelhaupt Cover Designer: Karen Montgomery Interior Designer: David Futato Illustrators: Robert Romano and Jessamyn Read Printing History: August 2007: Second Edition. September 2005: First Edition. Nutshell Handbook, the Nutshell Handbook logo, and the O’Reilly logo are registered trademarks of O’Reilly Media, Inc. Programming WPF, the image of a kudu, and related trade dress are trademarks of O’Reilly Media, Inc. Many of the designations used by manufacturers and sellers to distinguish their products are claimed as trademarks. Where those designations appear in this book, and O’Reilly Media, Inc. was aware of a trademark claim, the designations have been printed in caps or initial caps. While every precaution has been taken in the preparation of this book, the publisher and authors assume no responsibility for errors or omissions, or for damages resulting from the use of the information contained herein. This book uses RepKover™, a durable and flexible lay-flat binding. ISBN-10: 0-596-51037-3 ISBN-13: 978-0-596-51037-4 [C] Abi: Thank you for everything. My parents: Thank you for making it all possible. —Ian Griffiths My wife and my sons: You define the heaven that exceeds my grasp. Both my parents: You made me love reading from the beginning. I was happy that you passed on the secret writer gene (not to mention surprised). —Chris Sells v Table of Contents Forewords . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . xi Preface . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . xv 1. Hello, WPF . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1 WPF from Scratch 1 XAML Browser Applications (XBAPs) 14 Content Models 16 Layout 19 Controls 22 Data Binding 22 Dependency Properties 27 Resources 28 Styles 30 Animation 31 Control Templates 32 Graphics 33 3D 34 Documents and Printing 34 2. Applications and Settings . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 36 Application Lifetime 36 Application Deployment 48 Settings 55 vi | Table of Contents 3. Layout . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 61 Layout Basics 61 StackPanel 62 WrapPanel 65 DockPanel 66 Grid 69 Canvas 84 Viewbox 86 Common Layout Properties 89 When Content Doesn’t Fit 99 ScrollViewer 101 Custom Layout 105 4. Input . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 109 Routed Events 109 Mouse Input 117 Keyboard Input 120 Ink Input 122 Commands 124 Code-Based Input Handling Versus Triggers 137 5. Controls . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 139 What Are Controls? 139 Buttons 141 Slider and Scroll Controls 144 ProgressBar 145 Text Controls 146 ToolTip 149 GroupBox and Expander 150 List Controls 152 Menus 160 Toolbars 164 GridSplitter 166 6. Simple Data Binding . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 168 Without Data Binding 168 Data Binding 177 Debugging Data Binding 198 Table of Contents | vii 7. Binding to List Data . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 200 Binding to List Data 200 Data Source Providers 228 Master-Detail Binding 245 Hierarchical Binding 252 8. Styles . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 257 Without Styles 257 Inline Styles 261 Named Styles 262 Element-Typed Styles 268 Data Templates and Styles 271 Triggers 275 9. Control Templates . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 284 Beyond Styles 284 Logical and Visual Trees 305 Data-Driven UI 308 10. Windows and Dialogs . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 314 Window 314 Dialogs 322 11. Navigation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 341 NavigationWindow 341 Pages 342 Frames 359 XBAPs 361 Navigation to HTML 363 12. Resources . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 365 Creating and Using Resources 365 Resources and Styles 378 Binary Resources 383 Global Applications 389 13. Graphics . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 395 Graphics Fundamentals 395 Shapes 406 Bitmaps 429 viii | Table of Contents Brushes and Pens 439 Transformations 461 Visual Layer Programming 463 14. Text and Flow Documents . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 468 Fonts and Text Styles 468 Text and the User Interface 478 Text Object Model 493 Typography 519 15. Printing and XPS . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 522 XPS 522 XPS Document Classes 524 Generating XPS Output 533 XPS File Generation Features 543 System.Printing 555 Displaying Fixed Documents 561 16. Animation and Media . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 563 Animation Fundamentals 563 Timelines 579 Keyframe Animations 593 Path Animations 598 Clocks and Control 601 Transition Animations 605 Audio and Video 608 17. 3D Graphics . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 612 3D Content in a 2D World 612 Cameras 613 Models 618 Lights 629 Textures 635 Transforms 637 3D Data Visualization 642 Hit Testing 648 Table of Contents | ix 18. Custom Controls . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 651 Custom Control Basics 651 Choosing a Base Class 652 Custom Functionality 655 Supporting Templates in Custom Controls 668 Default Styles 674 UserControl 676 Adorners 678 A. XAML . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 683 B. Interoperability . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 715 C. Asynchronous and Multithreaded WPF Programming . . . . . . . . . . . . . . . . . 738 D. WPF Base Types . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 750 E. Silverlight . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 766 Index . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 821 xi Forewords 1 First Edition Over the past two-plus years, my day job has involved XAML-izing various parts of the Microsoft universe. My standard refrain when encountering XAML newbies has been “read the XAML appendix from Chris and Ian’s book.” That appendix (origi- nally printed in the beta edition of this book) was easily the most direct and to-the- point treatment of the topicI’ve seen, and several dozen of my coworkersgot their first taste of XAML from Ian’s excellent writing. (Ian wrote the XAML appendix.) Over the past year, as I’ve started to make the transition from runtime plumber to pixel pusher, the chapters on WPF proper were super-efficient in getting me off the ground (things have changed a lot since I wrote my last WndProc). At the time this edition hits the shelves, there are numerous books dedicated to WPF, written by some pretty notable folks. This book is unique in that Ian has been telling the story on the road for a couple of years getting the right balance of concep- tual understanding and pragmatic “everyone screws this up” experience. I know from personal experience that there’s nothing like teaching to hone a story to perfec- tion—this book is evidence of that. Ian’s co-author should thank his lucky stars that Ian was willing to travel the planet trying out the material rather than taking a cushy job in Windows. Now that they’ve gotten this book out, maybe Ian should take a cushy job, too. He’s certainly earned it. Second Edition Wow, I can’t believe that after all that time in the chute, .NET 3.0 and Windows Vista have finally shipped. I vividly remember scrambling backstage at PDC 2003 with Chris trying to ready the first live demonstration of .NET 3.0 (then called WinFX) for the keynote speaker, xii | Forewords Jim Allchin. It was an especially stressful keynote because Los Angeles was plagued with brush fires at the time and Chris Anderson’s flight had been canceled; fortu- nately Chris Sells had already arrived and was ready to pinch-hit both in preparation and presentation if Chris, in fact, couldn’t make it to L.A. in time. At the time, Chris’ job at Microsoft was to make sure that Vista—including WPF—was a smashing suc- cess. Little did he know it would take almost four years until the product actually shipped (which of course is a prerequisite for success). So, what’s the big deal with WPF? Like its sister .NET 3.0 technology, Windows Workflow Foundation (WF), WPF embraces the “it takes a village” approach to software development and uses XAML to allow people with different skill sets to collaborate in the development process. In the case of WF, XAML lets high-level process and rule descriptions integrate with imperative code written in C# or Visual Basic. In the case of WPF, XAML is the bridge between us code monkeys and the beret-wearing, black-turtleneck set who design visuals that look like they weren’t designed by, well, us code monkeys. WPF really is an impressive piece of technology: documents, forms, and multimedia all wrapped up nicely in a markup- and code-friendly package. What I find even more impressive is the fact that Chris found the time outside his day job to pull together the book you’re holding in your hands right now, capturing those four-plus years of experience with WPF (including screenshots!) into a digest- ible and portable form. I’ve had the good fortune of having many conversations with Chris over the years about the nuances of WPF—sometimes on the phone, sometimes in his office (it’s across the hall from mine), and sometimes at the poker table. This book has taught me a whole lot more. Now that it’s all shipped, let the light blinking begin! —Don Box Architect, Microsoft When I joined Microsoft 11 years ago, I first worked in the IT group, building applica- tions to help the Microsoft sales force analyze data. I developed using Visual Basic 4.0 on early versions of Windows 95 and Windows NT 3.51 before moving over to work on the development team for Visual Basic5.0, and later, 6.0. As time went on, I worked on Visual J++, Windows Foundation Classes, .NET, Windows Forms, ASP.NET, and eventually the Windows Presentation Foundation (WPF). Forewords | xiii When I learned to program Windows, I read the book that was considered the “bible” of Windows programming at the time, Programming Windows 3.1 by Charles Petzold (Microsoft Press). After helping to build the next-generation programming platform for Microsoft—the .NET Framework—I was first introduced to Chris Sells because he’d written the “bible” of programming .NET client applications: Windows Forms Programming (Addison-Wesley). Later, while I was building WPF, Chris and Ian were already writing the first book for that technology, too. As part of his work, Chris provided feedback on early versions of WPF, drawing on his extensive experi- ence as a preeminent author and educator for programming client applications for Windows. In fact, based on his sensibilities, we actually refer to a customer-focused style of system design used in my group as the “Sellsian” approach. Of course, Chris didn’t write this book all by himself. Ian Griffiths is a tremen- dously gifted technologist with a pedigree that includes working with Develop- Mentor and now Pluralsight as a consultant, developer, speaker, and author (his works include .NET Windows Forms in a Nutshell [O’Reilly]), focusing on a wide range of technologies including Windows Forms and WPF. I’ve had less opportunity to spend time with Ian; however, in every interaction with him, I have been amazed! Chris and Ian have both followed client technology since the early days of Windows. While I have spent my career building platforms, Chris and Ian have spent their careers making them accessible to a broad range of developers. As Chris puts it, they’ve been “following along behind [me] with a broom and a dustpan, cleaning up [my] messes for years.” This book is a thorough and comprehensive dive into WPF. Chris and Ian’s unique approach to explaining and building software illuminates the corners and open vis- tas of the platform. When they bump into its limitations, they don’t just explain them, but they show you how to work around them and solve real-world problems. If you are looking for an exhaustive treatment of how to build applications using the Windows Presentation Foundation, this book deserves a spot on your shelf. —Chris Anderson Former architect of Windows Presentation Foundation xv Preface2 It’s been a long road to the Windows Presentation Foundation. I learned to program Windows from Programming Windows 3.1, by Charles Petzold (Microsoft Press). In those days, programming for Windows was about windows, menus, dialogs, and child controls. To make it all work, we had WndProcs (window procedure functions) and messages. We dealt with the keyboard and the mouse. If we got fancy, we would do some nonclient work. Oh, and there was the stuff in the big blank space in the middle that I could fill however I wanted with the graphics device interface (GDI), but my 2D geometry had better be strong to get it to look right, let alone perform adequately. Later I moved to the Microsoft Foundation Classes (MFC), where we had this thing called a “document,” which was separate from the “view.” The document could be any old data I wanted it to be and the view, well, the view was the big blank space in the middle that I could fill however I wanted with the MFC wrappers around GDI. Later there was this thing called DirectX, which was finally about providing tools for filling in the space with hardware-accelerated 3D polygons, but DirectX was built for writing full-screen games, so using it to build content visualization and management applications just made my head hurt. Windows Forms, on the other hand, was such a huge productivity boost and I loved it so much that I wrote a book about it (as did my coauthor). Windows Forms was built on top of .NET, a managed environment that took a lot of programming minu- tiae off my hands so that I could concentrate on the content. Plus, Windows Forms itself gave me all kinds of great tools for laying out my windows, menus, dialogs, and child controls. And the inside of the windows where I showed my content? Well, if the controls weren’t already there to do what I wanted, I could draw the content however I wanted using the GDI+ wrappers in System.Drawing, which was essen- tially the same drawing model Windows programmers had been using for the past 20 years, before even hardware graphics acceleration in 2D, let alone 3D. xvi | Preface In the meantime, a whole other way of interacting with content came along: HTML. HTML was great at letting me arrange my content, both text and graphics, and it would flow it and reflow it according to the preferences of the user. Further, with the recent emergence of AJAX (Asynchronous JavaScript and XML), this environment gets even more capable. Still, HTML isn’t so great if you want to control more of the user experience than just the content, or if you want to do anything Windows-specific, both things that even Windows 3.1 programmers took for granted. More recently, the Windows Presentation Foundation (WPF) happened. Initially it felt like another way to create my windows, menus, dialogs, and child controls. However, WPF shares a much deeper love for content than has yet been provided by any other Windows programming framework. To support content at the lowest levels, WPF merges controls, text, and graphics into one programming model; all three are placed into the same element tree in the same way. And although these primitives are built on top of DirectX to leverage the 3D hardware acceleration that is dormant when you’re not running the latest twitch game, they’re also built into .NET, providing the same productivity boost to WPF programmers that Windows Forms programmers enjoy. One level up, WPF provides its “content model,” which allows any control to host any group of other controls. You don’t have to build special BitmapButton or IconComboBox classes; you put as many images, shapes, videos, 3D models, or what- ever into a Button (or a ComboBox, ListBox, etc.) as suit your fancy. To help you arrange the content, whether in fixed or flow layout, WPF provides con- tainer elements that implement various layout algorithms in a way that is completely independent of the content they’re holding. To help you visualize the content, WPF provides data binding, control templates, and animation. Data binding produces and synchronizes visual elements on the fly based on your content. Control templates allow you to replace the complete look of a control while maintaining its behavior. Animation brings your user interface con- trol to life, giving your users immediate feedback as they interact with it. These fea- tures give you the power to produce data visualizations so far beyond the capabilities of the data grid, the pinnacle most applications aspire to, that even Edward Tufte would be proud. Combine these features with ClickOnce for the deployment and update of your WPF applications, both as standalone clients and as blended with your web site inside the browser, and you’ve got the foundation of the next generation of Win- dows applications. Preface | xvii The next generation of applications is going to blaze a trail into the unknown. WPF represents the best of the control-based Windows and content-based web worlds, combined with the performance of DirectX and the deployment capabilities of Click- Once, building for us a vehicle just itching to be taken for a spin. And like the intro- duction of fonts to the PC, which produced “ransom note” office memos, and the invention of HTML, which produced blinking online brochures, WPF is going to produce its own accidents along the road. Before we learn just what we’ve got in WPF, we’re going to see a lot of strange and wonderful sights. I can’t tell you where we’re going to end up, but with this book, I hope to fill your luggage rack so that you can make the journey. The good news is that you will not be traveling alone. In the period between the first and second editions of this book, a large user base has sprung up, providing all kinds of information and real-world applications to inspire you. A tiny sampling of the best of this information is listed here: • Tim Sneath’s big list of great WPF applications: http://blogs.msdn.com/tims/ search.aspx?q=%22great+wpf+applications%22 (http://tinysells.com/114) • Tim Sneath’s big list of WPF blogs: http://blogs.msdn.com/tims/articles/475132.aspx (http://tinysells.com/115) • Karsten Januszewski’s Five-Day Course for Hitting the WPF Curve/Cliff: http:// blogs.msdn.com/karstenj/archive/2006/06/15/632639.aspx (http://tinysells.com/116) • Microsoft’s WPF community site: http://wpf.netfx3.com • The MSDN WPF home page: http://msdn2.microsoft.com/en-us/netframework/ aa663326.aspx (http://tinysells.com/117) • CodeProject’s WPF section: http://www.codeproject.com/WPF (http://tinysells.com/ 118) • thirteen23’s inspirational set of WPF lab experiments: http://www.thirteen23.com/ labs.html (http://tinysells.com/119) • Lee Brimelow’s set of WPF designer tutorials: http://contentpresenter.com —Chris Sells Who This Book Is For As much as I love the designers of the world, who are going to go gaga over WPF, this book is aimed squarely at my people: developers. We’re not teaching program- ming here, so having experience with some sort of programming environment is a must before you read this book. Programming in .NET and C# is pretty much required; Windows Forms, XML, and HTML are all recommended. xviii | Preface How This Book Is Organized Here’s what each chapter of this book will cover: Chapter 1, Hello, WPF This chapter introduces the basics of WPF. It then provides a whirlwind tour of the features that we will cover in the following chapters, so you can see how everything fits together before we delve into the details. Chapter 2, Applications and Settings In this chapter, we show how WPF manages application-wide concerns, such as the lifetime of your process, keeping track of open windows, and storing application- wide states and settings. We also show your options for deploying applications to end users’ machines using ClickOnce. Chapter 3, Layout WPF provides a powerful set of tools for managing the visual layout of your applications. This chapter shows how to use this toolkit, and how to extend it. Chapter 4, Input This chapter shows how to make your WPF application respond to user input. We illustrate low-level input event handling, and the higher-level command system. Chapter 5, Controls Controls are the building blocks of a user interface. This chapter describes the controls built into the WPF framework. Chapter 6, Simple Data Binding All applications need to present information to the user. This chapter shows how to use WPF’s data binding features to connect the user interface to your underlying data. Chapter 7, Binding to List Data This chapter builds on the preceding one, showing how data binding works with lists of items. It also shows how to bind to hierarchical data. Chapter 8, Styles WPF’s styling mechanism provides a powerful way to control your application’s appearance while ensuring its consistency. Chapter 9, Control Templates WPF provides an astonishing level of flexibility in how you can customize the appearance of your user interface and the controls it contains. This chapter examines these facilities, showing how you can modify the appearance of built- in controls. Chapter 10, Windows and Dialogs WPF’s Window class is the basis for your main application windows. It also pro- vides the facilities necessary to build dialog windows. Preface | xix Chapter 11, Navigation As well as supporting traditional single window and cascading window applica- tions, WPF offers support for a web-like navigation style of user interface. This chapter shows how to use these services either for your whole application, or within a nested frame as part of a window. It also shows the “XBAP” deploy- ment model, which allows a WPF application to be hosted in a web browser. Chapter 12, Resources This chapter describes WPF’s resource handling mechanisms, which are used for managing styles, themes, and binary resources such as graphics. Chapter 13, Graphics WPF offers a powerful set of drawing primitives. It also offers an object model for manipulating drawings once you have created them. Chapter 14, Text and Flow Documents WPF offers support for high-quality rendering of formatted text throughout the user interface. This chapter explains the text services available wherever text is used, and the text object model that defines how text is formatted. It also describes how to use FlowDocuments to present large volumes of mixed text and graphics, in a way that is optimized for on-screen viewing. Chapter 15, Printing and XPS This chapter describes WPF’s printing services. Printing in WPF is very closely tied to XPS—the XML Paper Specification. This fixed-format document format allows printable output to be written into a file. The chapter explores both the XPS file format, and the APIs for printing and generating XPS documents. Chapter 16, Animation and Media This chapter describes WPF’s animation facilities, which allow most visible aspects of a user interface, such as size, shape, color, and position, to be ani- mated. It also describes the media playback services, which allow video and audio to be synchronized with animations. Chapter 17, 3D Graphics WPF applications can host 3D models in their user interface. Two-dimensional graphics and user interfaces can also be projected onto 3D surfaces. This chap- ter describes the 3D API, and shows how the worlds of 2D and 3D come together in WPF. Chapter 18, Custom Controls This chapter shows how to write custom controls and other custom element types. It shows how to take full advantage of the WPF framework to build con- trols as powerful and flexible as those that are built-in. Appendix A, XAML The eXtensible Application Markup Language (XAML) is an XML-based lan- guage that can be used to represent the structure of a WPF user interface. This appendix describes how XAML is used to create graphs of objects. xx | Preface Appendix B, Interoperability WPF is able to coexist with old user interface technologies, enabling developers to take advantage of WPF without rewriting their existing applications. This appendix describes the interoperability features that make this possible. Appendix C, Asynchronous and Multithreaded WPF Programming Multithreaded code and asynchronous programming are important techniques for making sure your application remains responsive to user input at all times. This appendix explains WPF’s threading model, and shows how to make sure your threads coexist peacefully with a WPF UI. Appendix D, WPF Base Types WPF has a large and complex class inheritance hierarchy. Understanding the roles of all these types and the relationships between them can be very daunting when you first approach WPF. This appendix singles out the most important types, and explains how they fit into WPF. Appendix E, Silverlight Although WPF’s XBAP model allows WPF applications to run inside a web browser, this requires that .NET 3.0 be installed on an end user’s machine. This makes WPF unsuitable for applications that need to be accessible from platforms other than Windows. However, WPF’s cousin, Silverlight, is a cross-platform solu- tion, offering a subset of the services available in WPF. This appendix provides a quick introduction to Silverlight from Shawn Wildermuth, Microsoft MVP. What You Need to Use This Book This book targets Visual Studio 2005 and the .NET Framework 3.0, which includes WPF (among other things). You’ll also want the Visual Studio 2005 extensions that provide WPF templates that are mentioned in this book. You can download all of this for free* (even Visual Studio 2005, if you’re willing to limit yourself to Visual C# 2005 Express Edition†). WPF itself is supported on Windows XP, Windows Server 2003, and Windows Vista (and will be supported on future versions of Windows, of course). * You can find the links to download the .NET Framework 3.0 and the WPF extensions to Visual Studio at http://sellsbrothers.com/news/showTopic.aspx?ixTopic=2053 (http://tinysells.com/104). † You can download Visual C# Express from http://msdn.microsoft.com/vstudio/express/downloads (http:// tinysells.com/105). Preface | xxi Conventions Used in This Book The following typographical conventions are used in this book: Italic Indicates new terms. Constant width Indicates code, commands, options, switches, variables, attributes, keys, func- tions, types, classes, namespaces, methods, modules, properties, parameters, val- ues, objects, events, event handlers, XML tags, HTML tags, macros, the contents of files, or the output from commands. Constant width bold Shows code or other text that should be noted by the reader. Constant width italic Indicates code that should be replaced with user-supplied values. Constant width ellipses (...) Shows code or other text not relevant to the current discussion. This icon signifies a tip, suggestion, or general note. This icon signifies a warning or caution. Using Code Examples This book is here to help you get your job done. In general, you may use the code in this book in your programs and documentation. You do not need to contact us for permission unless you’re reproducing a significant portion of the code. For example, writing a program that uses several chunks of code from this book does not require permission. Selling or distributing a CD-ROM of examples from O’Reilly books does require permission. Answering a question by citing this book and quoting example code does not require permission. Incorporating a significant amount of example code from this book into your product’s documentation does require permission. We appreciate, but do not require, attribution. An attribution usually includes the title, author, publisher, and ISBN. For example: “Programming WPF, Second Edition, by Chris Sells and Ian Griffiths. Copyright 2007 O’Reilly Media Inc., 978-0-596-51037-4.” If you feel your use of code examples falls outside fair use or the permission given above, feel free to contact us at permissions@oreilly.com. xxii | Preface How to Contact Us Please address comments and questions concerning this book to the publisher: O’Reilly Media, Inc. 1005 Gravenstein Highway North Sebastopol, CA 95472 800-998-9938 (in the United States or Canada) 707-829-0515 (international or local) 707-829-0104 (fax) For the code samples associated with this book and for errata, visit the web site maintained by the authors at: http://sellsbrothers.com/writing/wpbook To contact Ian Griffiths, visit: http://www.interact-sw.co.uk/iangblog/ To contact Chris Sells, visit: http://sellsbrothers.com The publisher maintains a web page for this book at: http://www.oreilly.com/catalog/9780596510374 To comment or ask technical questions about this book, send email to: bookquestions@oreilly.com For more information about our books, conferences, Resource Centers, and the O’Reilly Network, see our web site at: http://www.oreilly.com Safari® Books Online When you see a Safari® Books Online icon on the cover of your favorite technology book, that means the book is available online through the O’Reilly Network Safari Bookshelf. Safari offers a solution that’s better than e-books. It’s a virtual library that lets you easily search thousands of top tech books, cut and paste code samples, download chapters, and find quick answers when you need the most accurate, current informa- tion. Try it for free at http://safari.oreilly.com. Preface | xxiii Ian’s Acknowledgments Writing this book wouldn’t have been possible without the support and feedback generously provided by a great many people. I would like to thank the following: The readers, without whom this book would have a rather sad, lonely, and pointless existence. My coauthor, Chris Sells, both for getting me involved in writing about WPF in the first place, and for his superb feedback and assistance. Shawn Wildermuth, for contributing the Silverlight appendix, and enduring Chris’s and my uncompromising approach to technical review. Tim Sneath, both for his feedback and for providing me with the opportunity to meet and work with many members of the WPF team. Microsoft employees and contractors, for producing a technology I like so much that I just had to write a book about it. And in particular, thank you to those people at Microsoft who gave their time to answer my questions or review draft chapters, including Chris Anderson, Marjan Badiei, Jeff Bogdan, Mark Boulter, Ben Carter, Dennis Cheng, Karen Corby, Vivek Dalvi, Nathan Dunlap, Ifeanyi Echeruo, Pablo Fernicola, Filipe Fortes, Kevin Gjerstad, Aaron Goldfeder, John Gossman, Mark Grinols, Namita Gupta, Henry Hahn, Robert Ingebretson, Kurt Jacob, David Jenni, Michael Kallay, Amir Khella, Adam Kinney, Nick Kramer, Lauren Lavoie, Daniel Lehenbauer, Kevin Moore, Elizabeth Nelson, Seema Ramchandani, Rob Relyea, Chris Sano, Greg Schechter, Eli Schleifer, Ashish Shetty, Adam Smith, Michael Stokes, Zhanbo Sun, David Teitlebaum, Stephen Turner, and Dawn Wood. The following non-Microsoft people for their direct or indirect contributions to the quality of this book: Matthew Adams, Craig Andera, Richard Blewett, Keith Brown, Ryan Dawson, Kirk Fertitta, Kenny Kerr, Drew Marsh, Dave Minter, Brian Noyes, Fritz Onion, Aaron Skonnard, Dan Sullivan, Bill Williams, and Zhou Yong. John Osborn and Caitrin McCullough at O’Reilly for their support throughout the writing process. The technical review team: Chris Anderson, Elsa Bartley, Patrick Cauldwell, Dennis Cheng, Arik Cohen, Beatriz de Oliveira Costa, Glyn Griffiths, Scott Hanselman, Karsten Januszewski, Nikola Mihaylov, Mark Miller, EricStollnitz, and Jeff Tentschert. And particular thanks to Mike Weinhardt for his extensive and thought- ful feedback. Finally, I especially want to thank Abi Sawyer for all her support, and for putting up with me while I wrote this book—thank you! xxiv | Preface Chris’s Acknowledgments I’d like to thank the following people, without whom I wouldn’t have been able to write either the first or second edition of this book: The readers. When you’ve got a story to tell, you’ve got to have someone to tell it to. I’ve been writing about WPF in various forums for almost four years and the readers have always pushed and encouraged me further. My coauthor, Ian Griffiths. Ian has an extensive background in all things graphical and video-related, including technologies so deep I can’t understand him half the time. This, in addition to his vast experience teaching the WPF course and writing real-world WPF applications, along with his wonderful writing style, made him the perfect coauthor on this book. I couldn’t have asked for better. Shawn Wildermuth, for the cutting-edge Silverlight appendix. Shawn’s been doing a bunch of advanced Silverlight work, so when I asked him to add his knowledge to this book, he graciously agreed, completely unaware of the buzz saw that is the Griffith/ Sells reviewing process. Sorry, Shawn, and thanks! Kenny Kerr, for his most excellent Window Clippings tool. His tool, plus the fea- tures he added at my request, saved me countless hours of work and produced much higher-quality screenshots than I would’ve normally had the patience to capture. Chango Valtchev and Michael Weinhardt, for their huge help on navigation and the pitfalls thereof. The material in Chapter 11 was influenced very much by Chango and Michael. Microsoft employees and contractors (in the order in which I found them in my WPF email folder): Mark Lawrence, Robert Wlodarczyk, Hua Wang, Worachai Chaowe- eraprasit, Preeda Ola, Varsha Mahadevan, Larry Golding, Benjamin Westbrook, Ben Constable, Brian Chapman, Niklas Borson, Ryan Molden, Hamid Mahmood, Lau- ren Lavoie, Lars Bergstrom, Amir Khella, Kevin Kennedy, David Jenni, Elizabeth Nelson, Beatriz de Oliveira Costa, Nick Kramer, Allen Wagner, Chris Sano, Tim Sneath, Steve White, Matthew Adams, Eli Schleifer, Karsten Januszewski, Rob Rel- yea, Mark Boulter, Namita Gupta, John Gossman, Kiran Kumar, Filipe Fortes, Guy Smith, Zhanbo Sun, Ben Carter, Joe Marini, Dwayne Need, Brad Abrams, Feng Yuan, Dawn Wood, Vivek Dalvi, Jeff Bogdan, Steve Makofsky, Kenny Lim, Dmitry Titov, Joe Laughlin, Arik Cohen, EricStollnitz, Pablo Fernicola,Henry Hahn, Jamie Cool, Sameer Bhangar, and Brent Rector. I regularly spammed a wide range of my Microsoft brethren and instead of snubbing me, they answered my email questions, helped me make things work, gave me feedback on the chapters, sent me additional information without an explicit request, and in the case of John Gossman, for- warded the chapters along to folks with special knowledge so that they could give me feedback. This is the first book I’ve written “inside,” and with the wealth of informa- tion and conscientious people available, it’d be very, very hard to go back to writing “outside.” Preface | xxv The external technical reviewers, who provide an extremely important mainstream point of view that Microsoft insiders can’t: Craig Andera, Chris Anderson, Elsa Bart- ley, Patrick Cauldwell, Dennis Cheng, Arik Cohen, Beatriz de Oliveira Costa, Ryan Dawson, Glyn Griffiths, Scott Hanselman, Karsten Januszewski, Adam Kinney, Drew Marsh, Nikola Mihaylov, Mark Miller, Dave Minter, Brian Noyes, Eric Stollnitz, and Jeff Tentschert. Glyn Griffiths, not just for raising Ian right, but also for his eagle eye as the last reviewer of what we thought was the “final” manuscript. Not only did he catch a frightening number of grammatical errors, but he also pointed out the copyedits from the first edition of the book that we’d failed to reverse-integrate into our Word documents for the second edition. He literally did a three-way diff for us, which was impressive and spooky at the same time... Caitrin McCullough and John Osborn from O’Reilly Media, for supporting me in breaking a bunch of the normal ORA procedures and guidelines to publish the book I wanted. Shawn Morrissey, for letting me make writing a part of my first two years at Microsoft, and even giving me permission to use some of that material to seed this book. Shawn put up with me, trusting me to do my job remotely when very few Microsoft managers would. Don Box, for setting my initial writing quality bar and hitting me squarely between the eyes until I could clear it. Of course, thank you for the foreword and for acting as my soundboard on this preface. You’re an invaluable resource and a dear friend. Barbara Box, for putting me up in the Chez Box clubhouse while I balance work and family in a way that wouldn’t be possible without you. Chris Anderson, architect on WPF, for his foreword and a ton of illuminating con- versations even after he wrote a competing book. Chris is a very generous man. After I’d reviewed the first chapter of his book and realized that reading it was giving me insights that would affect my own writing, he wouldn’t let me stop. He cared most about getting the right story out there, and not at all about into which book it went. Michael Weinhardt, as the primary developmental editor on both editions of this book. His feedback is probably the single biggest factor in whatever quality we’ve been able to cram in. As if that wasn’t enough, he produced many of the figures in my chapters. (Ian, as a rule, is far more industrious than I.) Tim Ewald, for that critical eye at the most important spots in the first edition. My wife and sons. The first edition was the first book I’ve ever written while holding a full-time job and, worse than that, while I was learning a completely new job. Frankly, I neglected my family pretty thoroughly for about three solid months on the first edition and nearly six months on the second, but they understood and sup- ported me, like they have all of my endeavors over the years. I am very much looking forward to getting back to them (again). 1 Chapter 1 CHAPTER 1 Hello, WPF1 WPF is a completely new presentation framework, integrating the capabilities of many frameworks that have come before it, including User, GDI, GDI+, and HTML, as well as being heavily influenced by toolkits targeted at the Web, such as Adobe Flash, and popular Windows applications like Microsoft Word. This chapter will give you the basics of WPF from scratch, and then a whirlwind tour of the things you’ll read about in detail in the chapters that follow. WPF from Scratch Example 1-1 is pretty much the smallest WPF “application” you can write in C#. The STAThread attribute signals .NET to make sure that when COM is initialized on the application’s main thread, it’s initialized to be com- patible with single-threaded UI work, as required by WPF applications. Example 1-1. Minimal C# WPF application // MyApp.cs using System; using System.Windows; // the root WPF namespace namespace MyFirstWpfApp { class MyApp { [STAThread] static void Main( ) { // the WPF message box MessageBox.Show("Hello, WPF"); } } } 2 | Chapter 1: Hello, WPF In fact, this is such a lame WPF application that it doesn’t even use any of the ser- vices of WPF; the call to MessageBox.Show is just an interop call to Win32. However, it does require the same infrastructure required of other WPF applications, so it serves as a useful starting point for our explorations. Building Applications Building this application (Example 1-2) is a matter of firing off the C# compiler from a command shell with the appropriate environment variables.* (The command line here has been spread across multiple lines for readability, but you need to put it all on one line.) Here, we’re telling the C# compiler that we’d like to create a Windows application (instead of a Console application, which we get by default), putting the result, 1st.exe, into the current folder, referencing the three main WPF assemblies (WindowsBase, PresentationCore, and PresentationFramework), along with the core .NET System assembly, and compiling the MyApp.cs source file. Running the resulting 1st.exe produces the world’s lamest WPF application, as shown in Figure 1-1. * Start ➝ All Programs ➝ Microsoft Windows SDK ➝ CMD Shell. Example 1-2. Building a WPF application manually C:\1st> csc /target:winexe /out:.\1st.exe /r:System.dll /r:"C:\Program Files\Reference Assemblies\Microsoft\Framework\v3.0\WindowsBase.dll" /r:"C:\Program Files\Reference Assemblies\Microsoft\Framework\v3.0\PresentationCore.dll" /r:"C:\Program Files\Reference Assemblies\Microsoft\Framework\v3.0\ PresentationFramework.dll" MyApp.cs Microsoft (R) Visual C# 2005 Compiler version 8.00.50727.312 for Microsoft (R) Windows (R) 2005 Framework version 2.0.50727 Copyright (C) Microsoft Corporation 2001-2005. All rights reserved. Figure 1-1. A lame WPF application WPF from Scratch | 3 In anticipation of less lame WPF applications with more source files and more com- pilation options, let’s refactor the compilation command line into an msbuild project file (Example 1-3). The msbuild tool is a .NET 2.0 command-line application that understands XML files in the form shown in Example 1-3. The file format is shared between msbuild and Visual Studio 2005 so that you can use the same project files for both command- line and integrated development environment (IDE) builds. In this .csproj file (which stands for “C# Project”), we’re saying the same things we said to the C# compiler— in other words, we’d like a Windows application, we’d like the output to be 1st.exe in the current folder, and we’d like to reference the System assembly and the main WPF assemblies while compiling the MyApp.cs file. The actual smarts of how to turn these minimal settings into a compiled .NET application are contained in the .NET 2.0 Microsoft.CSharp.targets file that’s imported at the bottom of the file. Executing msbuild.exe on the 1st.csproj file looks like Example 1-4. Example 1-3. A minimal msbuild project file winexe .\ 1st.exe Example 1-4. Building using msbuild C:\1st>msbuild 1st.csproj Microsoft (R) Build Engine Version 2.0.50727.312 [Microsoft .NET Framework, Version 2.0.50727.312] Copyright (C) Microsoft Corporation 2005. All rights reserved. Build started 2/4/2007 2:24:46 PM. _________________________________________________ _ Project "C:\1st\1st.csproj" (default targets): Target PrepareForBuild: Creating directory "obj\Debug\". 4 | Chapter 1: Hello, WPF As I mentioned, msbuild and Visual Studio 2005 share a project file format, so load- ing the project file into Visual Studio is as easy as double-clicking on 1st.csproj (as shown in Figure 1-2). Unfortunately, as nice as the project file makes building our WPF application, the application itself is still lame. WPF Applications A real WPF application is going to need more than a message box. WPF applica- tions have an instance of the Application class from the System.Windows namespace. The Application class provides methods like Run for starting the application, events like Startup and SessionEnding for tracking lifetime, and properties like Current, ShutdownMode, and MainWindow for finding the global application object, choosing when it shuts down, and getting the application’s main window. Typically, the Application class serves as a base for custom application-wide data and behavior (Example 1-5). Target CoreCompile: C:\Windows\Microsoft.NET\Framework\v2.0.50727\Csc.exe /noconfig /nowarn:1701 ,1702 /reference:"C:\Program Files\Reference Assemblies\Microsoft\Framework\v3.0 \PresentationCore.dll" /reference:"C:\Program Files\Reference Assemblies\Microso ft\Framework\v3.0\PresentationFramework.dll" /reference:C:\Windows\Microsoft.NET \Framework\v2.0.50727\System.dll /reference:"C:\Program Files\Reference Assembli es\Microsoft\Framework\v3.0\WindowsBase.dll" /debug+ /out:obj\Debug\1st.exe /tar get:winexe MyApp.cs Target _CopyFilesMarkedCopyLocal: Copying file from "C:\Program Files\Reference Assemblies\Microsoft\Framework \v3.0\PresentationCore.dll" to ".\PresentationCore.dll". Copying file from "C:\Program Files\Reference Assemblies\Microsoft\Framework \v3.0\System.Printing.dll" to ".\System.Printing.dll". Copying file from "C:\Program Files\Reference Assemblies\Microsoft\Framework \v3.0\PresentationCore.xml" to ".\PresentationCore.xml". Copying file from "C:\Program Files\Reference Assemblies\Microsoft\Framework \v3.0\System.Printing.xml" to ".\System.Printing.xml". Target CopyFilesToOutputDirectory: Copying file from "obj\Debug\1st.exe" to ".\1st.exe". 1st -> C:\1st\1st.exe Copying file from "obj\Debug\1st.pdb" to ".\1st.pdb". Build succeeded. 0 Warning(s) 0 Error(s) Time Elapsed 00:00:04.15 Example 1-4. Building using msbuild (continued) WPF from Scratch | 5 Figure 1-2. Loading the minimal msbuild project file into Visual Studio Example 1-5. A less minimal WPF application // MyApp.cs using System; using System.Windows; namespace MyFirstWpfApp { class MyApp : Application { [STAThread] static void Main( ) { MyApp app = new MyApp(); app.Startup += app.AppStartup; app.Run(); } void AppStartup(object sender, StartupEventArgs e) { // By default, when all top level windows // are closed, the app shuts down Window window = new Window(); window.Title = "Hello, WPF"; window.Show(); } } } 6 | Chapter 1: Hello, WPF Here, our MyApp class derives from the Application base class. In Main, we create an instance of the MyApp class, add a handler to the Startup event, and kick things off with a call to the Run method. Our Startup handler creates our sample’s top-level window, which is an instance of the built-in WPF Window class, making our sample WPF application more interesting from a developer point of view, although visually less so, as shown in Figure 1-3. Although we can create instances of the built-in classes of WPF, such as Window, pop- ulating them and wiring them up from the application, it’s much more encapsulat- ing (not to mention abstracting) to create custom classes for such things, like the Window1 class (Example 1-6). Figure 1-3. A less lame WPF application Example 1-6. Window class declaring its own controls // Window1.cs using System; using System.Windows; using System.Windows.Controls; // Button et al namespace MyFirstWpfApp { class Window1 : Window { public Window1( ) { this.Title = "Hello, WPF"; // Do something interesting (sorta...) Button button = new Button( ); button.Content = "Click me, baby, one more time!"; button.Width = 200; button.Height = 25; button.Click += button_Click; this.Content = button; } void button_Click(object sender, RoutedEventArgs e) { MessageBox.Show( "You've done that before, haven't you...", "Nice!"); } } } WPF from Scratch | 7 In addition to setting its caption text, an instance of our Window1 class will include a button with its Content, Width, and Height properties set, and its Click event han- dled. With this initialization handled in the Window1 class itself, our app’s startup code looks a bit simpler (even though the application behavior itself has gotten “richer”; see Example 1-7). The results (after updating the .csproj file appropriately) are shown in Figure 1-4 and are unlikely to surprise you much. Example 1-7. Simplified Application instance // MyApp.cs using System; using System.Windows; namespace MyFirstWpfApp { class MyApp : Application { [STAThread] static void Main(string[] args) { MyApp app = new MyApp( ); app.Startup += app.AppStartup; app.Run( ); } void AppStartup(object sender, StartupEventArgs e) { // Let the Window1 initialize itself Window window = new Window1(); window.Show( ); } } } Figure 1-4. A slightly more interesting WPF application 8 | Chapter 1: Hello, WPF As the Window1 class gets more interesting, we’re mixing two very separate kinds of code: the “look,” represented by the initialization code that sets the window and child window properties, and the “behavior,” represented by the event handling code. As the look is something that you’re likely to want handled by someone with artisticsensibilities (a.k.a. turtleneck-wearingdesigner types) whereas the behavior is something you’ll want to leave to the coders (a.k.a. pocket-protector-wearing engi- neer types), separating the former from the latter would be a good idea. Ideally, we’d like to move the imperative “look” code into a declarative format suitable for tools to create with some drag-and-drop magic. For WPF, that format is XAML. XAML XAML is an XML-based language for creating and initializing .NET objects. It’s used in WPF as a human-authorable way of describing the UI, although you can use it for a much larger range of CLR types than just those in WPF. Example 1-8 shows how we declare the UI of our Window-derived class using XAML. The root element, Window, is used to declare a portion of a class, the name of which is contained in the Class attribute from the XAML XML namespace (declared with a prefix of “x” using the “xmlns” XML namespace syntax). The two XML namespace declarations pull in two commonly used namespaces for XAML work, the one for XAML itself (the one with the “x” prefix) and the one for WPF (which we’ve declared as the default for this XML file). You can think of the XAML in Example 1-8 as creating the partial class definition in Example 1-9. Example 1-8. Declaring a Window in XAML Example 1-9. C# equivalent of XAML from Example 1-8 namespace MyFirstWpfApp { partial class Window1 : Window { Button button; WPF from Scratch | 9 XAML was built to be as direct a mapping from XML to .NET as possible. Gener- ally, a XAML element is a .NET class name and a XAML attribute is the name of a property or an event on that class. This makes XAML useful for more than just WPF classes; pretty much any old .NET class that exposes a default constructor can be ini- tialized in a XAML file. Notice that we don’t have the definition of the click event handler in this generated class. For event handlers and other initializations and helpers, a XAML file is meant to be matched with a corresponding code-behind file, which is a .NET language code file that implements behavior in code “behind” the look defined in the XAML. Tradi- tionally, this file is named with a .xaml.cs extension and contains only the things not defined in the XAML. With the XAML from Example 1-8 in place, we can reduce our single-buttoned main window code-behind file to the code in Example 1-10. void InitializeComponent( ) { // Initialize Window1 this.Title = "Hello, WPF"; // Initialize button button = new Button( ); button.Width = 200; button.Height = 25; button.Click += button_Click; this.AddChild(button); } } } Example 1-10. C# code-behind file // Window1.xaml.cs using System; using System.Windows; using System.Windows.Controls; namespace MyFirstWpfApp { public partial class Window1 : Window { public Window1( ) { InitializeComponent( ); } void button_Click(object sender, RoutedEventArgs e) { MessageBox.Show(...); } } } Example 1-9. C# equivalent of XAML from Example 1-8 (continued) 10 | Chapter 1: Hello, WPF Notice the partial keyword modifying the Window1 class, which signals to the compiler that the XAML-generated class is to be paired with this human-generated class to form one complete class, each depending on the other. The partial Window1 class defined in XAML depends on the code-behind partial class to call the InitializeComponent method and to handle the click event. The code-behind class depends on the partial Window1 class defined in XAML to implement InitializeComponent, thereby providing the look of the main window (and related child controls). Further, as mentioned, XAML is not just for visuals. For example, nothing is stop- ping us from moving most of the definition of our custom MyApp class into a XAML file (Example 1-11). This reduces the MyApp code-behind file to the event handler in Example 1-12. You may have noticed that we no longer have a Main entry point to create the instance of the application-derived class and call its Run method. That’s because WPF has a special project setting to specify the XAML file that defines the application class, which appears in the msbuild project file (Example 1-13). Example 1-11. Declaring an application in XAML Example 1-12. Application code-behind file // MyApp.xaml.cs using System; using System.Windows; namespace MyFirstWpfApp { public partial class MyApp : Application { void AppStartup(object sender, StartupEventArgs e) { Window window = new Window1( ); window.Show( ); } } } Example 1-13. Specifying the application’s XAML in the project file winexe .\ 1st.exe WPF from Scratch | 11 The combination of the ApplicationDefinition element and the .NET 3.0-specific Microsoft.WinFX.targets file produces an application entry point that will create our application for us. Also notice in Example 1-13 that we’ve replaced the MyApp.cs file with the MyApp.xaml.cs file, added the Window1.xaml.cs file, and included the win- dow’s corresponding XAML file as a Page element (we don’t do the same thing for the application’s XAML file, as it’s already referenced in the ApplicationDefinition element). The XAML files will be compiled into partial class definitions using the instructions in the Microsoft.WinFX.targets file. The DependentUpon element is there to associate a code-behind file with its XAML file. This isn’t necessary for the build process, but it’s useful for tools that want to show the association. For example, Visual Studio uses DependentUpon to show the code-behind file nested under the XAML file. This basic arrangement of artifacts (i.e., application and main windows each split into a XAML and a code-behind file) is such a desirable starting point for a WPF application that creating a new project using the “Windows Application (WPF)” project template from within Visual Studio 2005 gives you the same initial configura- tion, as shown in Figure 1-5. Editing XAML Now that we’ve seen the wonder that is declarative UI description in XAML, you may wonder, “Do I get all the fun of editing the raw XML, or are there some tools that can join in the fun, too?” The answer is “sort of.” For example, if you’ve got the .NET Framework 3.0 extensions for Visual Studio 2005 (the same extensions that give you the WPF project templates in VS05), you will have a visual editor for XAML files that works very similarly to the built-in Windows Forms Designer. It will trigger by default when you double-click a file in the Solution Explorer, or you can right-click on Window1.xaml MyApp.xaml Example 1-13. Specifying the application’s XAML in the project file (continued) 12 | Chapter 1: Hello, WPF a XAML file in the Solution Expression and choose Open With. One of the options offered will be “WPF Designer (Cider)” (where “Cider” is the codename for the WPF Designer still under development). The WPF Designer allows for drag-and-drop-style construction of XAML files with elements from the Toolbox and setting properties in the property browser. In addition, you can see the XAML as the designer makes changes, and in fact, you can make changes in the XAML view itself and see those reflected in the designer. Figure 1-6 shows the WPF Designer in action. Unfortunately, as of the writing of this book, the WPF Designer is still very much under development and such basic features as visually add- ing event handlers, let alone more advanced features like data bind- ing, styles, control templates, and animation, are not supported, which is why you’re unlikely to do much with it. If you’re following along with the Visual Studio “Orcas” beta, you’ll get more current (and more full-featured) versions of the WPF Designer, but if you can’t wait, you have other choices, including two XAML designer tools (Microsoft Expression Blend and Microsoft Expression Design), a third- party XAML 3D editor (ZAM 3D), and several conversion tools from other popular vector drawing formats (e.g., Adobe Illustrator and Flash), all of which are currently downloadable at the time of this writing.* Figure 1-5. The result of running the WPF Application project template * Michael Swanson, the general manager of the Microsoft Platform Evangelist team, maintains a wonderful list of WPF-related first- and third-party tools and controls for your development enjoyment at http://blogs.msdn.com/ mswanson/articles/WPFToolsAndControls.aspx (http://tinysells.com/88). WPF from Scratch | 13 Another very useful tool for playing with XAML is the XamlPad tool that comes with the Windows SDK. It actually shows the visual representation of your XAML as you type it, as shown in Figure 1-7. XamlPad has some limitations; the most important is that it doesn’t allow code (e.g., x:Class or event handler declarations), but as instant gratification, it can’t be beat. Figure 1-6. The WPF Designer in action Figure 1-7. XamlPad in action 14 | Chapter 1: Hello, WPF WPF provides a number of services for applications that we haven’t covered, includ- ing lifetime management and ClickOnce-based deployment. In addition, although WPF doesn’t provide any direct support for application instance management or set- tings, the .NET 2.0 support for both of these features integrates with WPF. Chapter 2 covers all of these topics. XAML Browser Applications (XBAPs) While we’re talking about Visual Studio tools for WPF, you may notice that a few icons away from the “Windows Application (WPF)” project template is another one called “XAML Browser Application (WPF),” as shown in Figure 1-8. WPF itself was created as a unified presentation framework, meant to enable build- ing Windows applications with the best features from existing Windows application practice and existing web application practice. One of the nice things that web appli- cations provide is a single window showing the user one page of content/functionality at a time, allowing for navigation among the pages. For some applications, including Internet Explorer, the shell Explorer, Microsoft Money, and a bunch of Control Panel applets, this is thought to be preferable to the more common Windows application practice of showing more than one window at a time. To enable more of these kinds of applications, WPF provides the page, which is the unit of navigation in an XML Browser Application (XBAP). Instead of setting an application’s StartupUri to a XAML file that defines a window, we point an XBAP’s StartupUri at a XAML file that defines a page (Example 1-14). A WPF page is a class that derives from the Page class, as shown in Example 1-15. Figure 1-8. The WPF XAML Browser Application project template in VS05 Example 1-14. Starting with a Page instead of a Window XAML Browser Applications (XBAPs) | 15 } The primary way to allow the user to navigate in an XBAP is via the Hyperlink ele- ment, setting the NavigateUri to a relative URL of another XAML page in the project. The first page of our sample XBAP looks like Figure 1-9. In Figure 1-9, the hyperlinked text is underlined in blue, and if you were to move your mouse cursor over the hyperlink, it would show up as red. Further, the page’s WindowTitle property is set as the window caption. Of course, the most obvious thing to notice is that the XBAP is hosted inside the browser—Internet Explorer 7 to be exact. The reason for this is simple: XBAPs are meant to be deployed via the Web (which we’ll talk about later in this chapter) and to blend seamlessly with web pages. As you navigate among the pages in an XBAP, those pages are added to the naviga- tion history just as web pages would be, and you’re allowed to use the Internet Explorer toolbar to go backward and forward, as you’re used to doing. Example 1-15. A sample page Check out page 2, too. // Page1.xaml.cs ... namespace MyFirstXbapApp { public partial class Page1 : System.Windows.Controls.Page { public Page1( ) { InitializeComponent( ); } } } Figure 1-9. A simple XBAP hosted in Internet Explorer 7 16 | Chapter 1: Hello, WPF For example, let’s define page2.xaml as shown in Example 1-16. Clicking on the hyperlink on page 1 navigates to page 2, as shown in Figure 1-10. Notice in Figure 1-10 that the history for the back button is showing page 1, which is where we were just before getting to page 2. As you might imagine, there are many more topics to discuss to make your XBAPs integrate with the browser and still provide the rich functionality we expect from WPF applications. In addition, you can have any number of navigation windows in your standalone WPF applications. We cover these topics and more in Chapter 11. Content Models Although the different kinds of WPF application types are useful, the core of any pre- sentation framework is in the presentation elements themselves. In presentation sys- tems of old, fundamentally we had “chunks of look and behavior” (often called controls) and “containers of chunks of look and behavior.” In WPF, this character- ization doesn’t really hold up very well. Many elements that provide their own con- tent and behavior can also be containers of elements (and so on). As an example, let’s take a look at a Button. Example 1-16. Another simple page Hello and welcome to page 2. Figure 1-10. XBAP and navigation history Content Models | 17 The first thing that may surprise you about a WPF Button object is that you don’t need to use a string as the content; it will take any .NET object. You’ve already seen a string as a button’s content (see Example 1-17). However, as Example 1-18 shows, you can also use an image (see Figure 1-11). You can even use an arbitrary control, like a TextBox, as shown in Example 1-19 and Figure 1-12. Further, as you’ll see in Chapters 3 and 6, you can get fancy and show a collection of nested elements in a Button or even nonvisual objects as the content of a Button. The Button can take any object as content because it’s derived ultimately from a class called ContentControl, as are many other WPF classes (e.g., Label, ListBoxItem, ToolTip, CheckBox, RadioButton, and, in fact, Window itself). Example 1-17. A button with string content Example 1-18. A button with image content Figure 1-11. A button with image content Example 1-19. A button with control content 18 | Chapter 1: Hello, WPF A ContentControl knows how to hold anything that’s able to be rendered, not just a string. A ContentControl gets its content from the Content property, so you could specify a Button’s content like so (this is the longhand version of Example 1-17): Layout | 19 Because XML attributes can contain only one thing, property element syntax is espe- cially useful when you’ve got more than one thing to specify. For example, you might imagine a button with a string and an image defined, as in Example 1-22. Although the property element syntax can be useful for this kind of thing, in this par- ticular case it doesn’t work at all. This brings us to the second thing that may sur- prise you about content containment in WPF: many content containers can take only a single piece of content. For example, whereas a Button can take any old thing as content, it can take only a single thing which, without additional instructions, it will center and cause to fill up its entire client area. For more than one content element or a richer layout policy, you’ll need a panel. Layout Taking another look at Example 1-22 with the TextBlock and the Image as content for the Button, we don’t really have enough information to place them inside the area of the button. Should they be stacked left to right or top to bottom? Should one be docked on one edge and one docked to the other? How will things be stretched or arranged if the button resizes? These are questions best answered with a panel. A panel is a control that knows how to arrange its content. WPF comes with the fol- lowing general-purpose panel controls: Canvas Arranges content by position and size with no automatic rearrangement when the Canvas is resized DockPanel Arranges content according to the edge that each piece of content “docks” to, except for the last, which fills the remaining area Grid Arranges content in rows and columns as specified by the developer Example 1-21. Property element syntax with an image Example 1-22. You can’t have multiple things in a ContentControl 20 | Chapter 1: Hello, WPF StackPanel Arranges content top to bottom or left to right according to the orientation of the panel UniformGrid Arranges content in a grid with the same number of rows and columns gener- ated as needed to display the content WrapPanel Arranges things in a horizontal row until the next item won’t fit, in which case it wraps to the next row Grid Layout The most flexible panel by far is the grid, which arranges content elements in rows and columns, including the ability to span multiple rows and/or multiple columns, as shown in Example 1-23. Example 1-23 used the XAML property element syntax to define a grid with three rows and three columns inside the RowDefinition and ColumnDefinition elements. On each element, we’ve specified the Grid.Row and Grid.Column properties so that the grid knows which elements go where (the grid can have multiple elements in the same cell). One of the elements spans two rows and one spans two columns, as shown in Figure 1-13. Example 1-23. A sample usage of the Grid panel Layout | 21 Using the grid, we can be explicit about how we want to arrange an image with a text caption (Example 1-24). Figure 1-14 shows how the grid arranges the image and text for us. Because we’re just stacking one element on top of another, we could’ve used the stack panel, but the grid is so general-purpose that many WPF programmers find themselves using it for most layout configurations. XAML Attached Property Syntax You may have noticed that in setting up the Grid.Row and Grid.Panel attributes of the Button elements, we used another dotted syntax, similar to the property element syntax, but this time on the attribute instead of on the element. This is the attached property syntax, and it is used to set a property as associated with the particular ele- ment (e.g., a Button), but as defined by another element (e.g., a Grid). Figure 1-13. An example Grid panel in action Example 1-24. Arranging an image and text in a grid 22 | Chapter 1: Hello, WPF The attached property syntax is used in WPF as an extensibility mechanism. We don’t want the Button class to have to know that it’s being arranged in a Grid, but we do want to specify Grid-specific attributes on it. If the Button was being hosted in a Canvas, the Grid properties wouldn’t make any sense, so building Row and Column properties into the Button class isn’t such a great idea. Further, when we define our own custom panel that the WPF team never considered (e.g., HandOfCards), we want to be able to apply the HandOfCards-related attached properties to arbitrary elements it contains. This kind of extensibility is what the attached property syntax was designed for and it is common when arranging content on a panel. For the nitty-gritty of layout, including the other panels that I didn’t show, you’ll want to read Chapter 3. Controls Although the layout panels provide the container, the controls are the important things you’ll be arranging. So far, you’ve seen how to create instances of controls, set properties, and handle events. You’ve also seen the basics of the content models that make controls in WPF special. However, for the details of event routing, command handling, mouse/keyboard input, and an enumeration of the controls in WPF, you’ll want to check out Chapters 4 and 5. Further, for information about packaging up custom UI and behavior, you’ll want to read Chapter 18. Data Binding Once we’ve got a set of controls and a way to lay them out, we still need to fill them with data and keep that data in syncwith wherever the data actuallylives. (Controls are a great way to show data but a poor place to keep it.) For example, imagine that we’d like to build a WPF application for keeping track of people’s nicknames. Some- thing like Figure 1-15 would do the trick. Figure 1-14. A grid arranging an image and a text block Data Binding | 23 In Figure 1-15, we’ve got two TextBox controls, one for the name and one for the nickname. We’ve also got the actual nickname entries in a ListBox in the middle and a Button to add new entries. We could easily build the core data of such an applica- tion with a class, as shown in Example 1-25. Figure 1-15. Data binding to a collection of custom types Example 1-25. A custom type with data binding support public class Nickname : INotifyPropertyChanged { // INotifyPropertyChanged Member public event PropertyChangedEventHandler PropertyChanged; void Notify(string propName) { if( PropertyChanged != null ) { PropertyChanged(this, new PropertyChangedEventArgs(propName)); } } string name; public string Name { get { return name; } set { name = value; Notify("Name"); // notify consumers } } string nick; public string Nick { get { return nick; } set { nick = value; Notify("Nick"); // notify consumers } } public Nickname( ) : this("name", "nick") { } public Nickname(string name, string nick) { this.name = name; this.nick = nick; } } 24 | Chapter 1: Hello, WPF This class knows nothing about data binding, but it does have two public properties that expose the data, and it implements the standard INotifyPropertyChanged inter- face to let consumers of this data know when it has changed. In the same way that we have a standard interface for notifying consumers of objects when they change, we also have a standard way to notify consumers of collections of changes, called INotifyCollectionChanged. WPF provides an implementation of this interface, called ObservableCollection, which we’ll use so that appropriate events are fired when Nickname objects are added or removed (Example 1-26). Around these classes, we could build nickname management logic that looks like Example 1-27. Notice that the window’s class constructor adds a click event handler to add a new nickname and creates the initial collection of nicknames. However, the most useful thing that the Window1 constructor does is set its DataContext property so as to make the nickname data available for data binding. Example 1-26. A custom collection type with data binding support // Notify consumers public class Nicknames : ObservableCollection { } Example 1-27. Making ready for data binding // Window1.xaml.cs ... namespace DataBindingDemo { public class Nickname : INotifyPropertyChanged {...} public class Nicknames : ObservableCollection { } public partial class Window1 : Window { Nicknames names; public Window1( ) { InitializeComponent( ); this.addButton.Click += addButton_Click; // create a nickname collection this.names = new Nicknames( ); // make data available for binding dockPanel.DataContext = this.names; } void addButton_Click(object sender, RoutedEventArgs e) { this.names.Add(new Nickname( )); } } } Data Binding | 25 In WPF, data binding is about keeping object properties and collections of objects synchronized with one or more controls’ views of the data. The goal of data binding is to save you the time required to write the code to update the controls when the data in the objects changes, and to update the data when the user edits the data in the controls. The synchronization of the data to the controls depends on the INotifyPropertyChanged and INotifyCollectionChanged interfaces that we’ve been careful to use in our data and data collection implementations. For example, because the collection of our example nickname data and the nickname data itself both notify consumers when there are changes, we can hook up controls using WPF data binding, as shown in Example 1-28. This XAML lays out the controls as shown in Figure 1-15 using a dock panel to arrange things top to bottom and a text block to contain the editing controls. The secret sauce that takes advantage of data binding is the {Binding} values in the con- trol attributes instead of hardcoded values. By setting the Text property of the TextBox to {Binding Path=Name}, we’re telling the TextBox to use data binding to peek at the Name property out of the current Nickname object. Further, if the data changes in the Name TextBox, the Path is used to poke the new value back in. The current Nickname object is determined by the ListBox because of the IsSynchronizedWithCurrentItem property, which keeps the TextBox controls show- ing the same Nickname object as the one that’s currently selected in the ListBox. The ListBox is bound to its data by setting the ItemsSource attribute to {Binding} without a Path statement. In the ListBox, we’re not interested in showing a single property on a single object, but rather all of the objects at once. But how do we know that both the ListBox and the TextBox controls are sharing the same data? That’s where setting the dock panel’s DataContext comes in (back in Example 1-27). In the absence of other instructions, when a control’s property is set using data binding, it looks at its own DataContext property for data. If it doesn’t find Example 1-28. An example data binding usage Name: Nick: 26 | Chapter 1: Hello, WPF any, it looks at its parent and then its parent, and so on, all the way up the tree. Because the ListBox and the TextBox controls have a common parent that has a DataContext property set (the DockPanel), all of the data bound controls will share the same data. XAML Markup Extensions Before we take a look at the results of our data binding, let’s take a moment to dis- cuss XAML markup extensions, which is what you’re using when you set an attribute to something inside of curly braces (e.g., Text="{Binding Path=Name}"). Markup extensions add special processing to XAML attribute values. For example, this: is just a shortcut for this (which you’ll recognize as the property element syntax): For a complete discussion of markup extensions, as well as the rest of the XAML syntax, read Appendix A. Data Templates With the data binding markup syntax explained, let’s turn back to our example data binding application, which so far doesn’t look quite like what we had in mind, as seen in Figure 1-16. It’s clear that the data is making its way into the application, because the currently selected name and nickname are shown for editing. The problem is that, unlike the TextBox controls, which were each given a specific field of the Nickname object to show, the ListBox is expected to show the whole thing. Lacking special instructions, it’s call- ing the ToString method of each object, which results in only the name of the type. To show the data, we need to compose a data template, like the one in Example 1-29. Figure 1-16. ListBox showing objects of a custom type without special instructions Dependency Properties | 27 A data template is a set of elements that should be inserted somewhere. In our case, we are specifying a data template to be inserted for each listbox item by setting the ItemTemplate property. In Example 1-29, we’ve composed a data template from a text block that flows together two other text blocks, each bound to a property on a Nickname object separated by a colon, as shown back in Figure 1-15. At this point, we’ve got a completely data-bound application. As data in the collec- tion or the individual objects changes, the UI will be updated, and vice versa. How- ever, there is a great deal more to say on this topic, including binding to XML and relational data, master-detail binding, and hierarchical binding, which you’ll see in Chapters 6 and 7. Dependency Properties Although our data source Nickname object made its data available via standard .NET properties, we need something special to support data binding on the target ele- ment. Even though the TextContent property of the TextBlock element is exposed with a standard property wrapper, in order for it to integrate with WPF services like data binding, styling, and animation, it also needs to be a dependency property.A dependency property provides several features not present in .NET properties, including the ability to inherit its value from a container element, provide for object- independent storage (providing a potentially huge memory savings), and change tracking. Most of the time, you won’t have to worry about dependency properties versus .NET properties, but when you need the details, you can read about them in Chapter 18. Example 1-29. Using a data template : 28 | Chapter 1: Hello, WPF Resources Resources are named chunks of data defined separately from code and bundled with your application or component. .NET provides a great deal of support for resources, a bit of which we already used when we referenced tom.png from our XAML button earlier in this chapter. WPF also provides special support for resources scoped to ele- ments defined in the tree. As an example, let’s declare some default instances of our custom Nickname objects in XAML (see Example 1-30). Notice the Window.Resources, which is property element syntax to set the Resources property of the Window1 class. Here we can add as many named objects as we like, with the name coming from the Key attribute and the object coming from the XAML elements (remember that a XAML element is just a mapping to .NET class names). In this example, we’re creating a Nicknames collection named names to hold three Nickname objects, each constructed with the default constructor, and then setting each of the Name and Nick properties. Also notice the use of the StaticResource markup extension to reference the names resource as the collection to use for data binding. With this XAML in place, our win- dow construction reduces to the code shown in Example 1-31. Example 1-30. Declaring objects in XAML Name: Nick: ... Resources | 29 Now instead of creating the collection of names, we can pull it from the resources with the FindResource method. Just because this collection was created in XAML doesn’t mean that we need to treat it any differently than we treated it before, which is why the Add button event handler is the exact same code. Also, there’s no need to set the data context on the dock panel because that property was set in the XAML. For the full scoop on resources, including resource scoping and lookup, static and dynamic binding to resources, and using resources for theming and skinning, read Chapter 12. XAML Namespace Mapping Syntax Before we go on with resource applications, we need to discuss a new XAML syntax that’s come up: the mapping syntax. This provides the ability to bring in types not already known by the XAML compiler (in fact, the XAML compiler knows about only a couple of types). Our use of the mapping syntax looks like Example 1-32. Example 1-31. Finding a resource in code public partial class Window1 : Window { Nicknames names; public Window1( ) { InitializeComponent( ); this.addButton.Click += addButton_Click; // get names collection from resources this.names = (Nicknames)this.FindResource("names"); // no need to make data available for binding here //dockPanel.DataContext = this.names; } void addButton_Click(object sender, RoutedEventArgs e) { this.names.Add(new Nickname( )); } } Example 1-32. XAML mapping syntax ... ... 30 | Chapter 1: Hello, WPF When bringing a new CLR namespace into XAML, we use the XML namespace prefix mapping syntax. If we’ve got control of the CLR assembly in question, we can add an attribute to tag it with any URI we like. Otherwise, we have to use a specific format: xmlns:myPrefix="clr-namespace:MyNamespace[;assembly=MyAssembly]" The XML prefix is how we access the CLR namespace when referring to a CLR type in a XAML document (e.g., local:Nickname). I’ve chosen the XML namespace local in this case because the CLR namespace to which I’m referring must be part of the assembly being compiled along with the XAML in question. You can import CLR namespaces for another assembly by specifying the optional assembly attribute as part of the mapping. For a more thorough discussion of the namespace mapping syn- tax, including the attribute you can use to tag your CLR assemblies with URIs for more seamless mapping into XAML, read Appendix A. Styles One of the major uses for resources is to specify styles. A style is a set of property/ value pairs to be applied to one or more elements. For example, recall the two TextBlock controls from our Nickname sample, each of which was set to the same VerticalAlignment (Example 1-33). If we wanted to bundle the VerticalAlignment setting into a style, we could do this with a Style element in a Resources block (Example 1-34). Example 1-33. Multiple TextBlock controls with the same settings Name: Nick: ... Example 1-34. An example TextBlock style ... Name: Nick: ... Figure 1-17. Named style in action on two TextBlock controls Example 1-34. An example TextBlock style (continued) 32 | Chapter 1: Hello, WPF Control Templates In addition to changing a control’s look by manipulating properties, you can replace it with something completely different by setting a control’s Template property. In Example 1-35, we’ve decided that our Add button is a yellow ellipse, as shown in Figure 1-19. The template of a control in WPF is what defines the look, whereas the code defines the behavior. The default template comes from the system-scope resources (as described in Chapter 12), but if you don’t like that one, you can replace it with what- ever you like, using a content presenter to drop in the content provided by the devel- oper using your control. However, the behavior remains the same (e.g., if you click on the ellipse-shaped button in Figure 1-19, a Click event is still fired). We explore in detail the power of replacing the look of a control in Chapter 9. Figure 1-18. Buttons with animated glow (Color Plate 1) Example 1-35. Replacing a control’s look completely with a control template Figure 1-19. A yellow ellipse button Graphics | 33 Graphics When building up a control’s template, you’ll likely build it with a set of graphics primitives that WPF provides, including rectangles, polygons, lines, ellipses, and so on. WPF also lets you affect the way it renders graphics in any element, offering facil- ities that include bordering, rotating, or scaling another shape or control. WPF’s sup- port for graphics is engineered to fit right into the content model we’re already familiar with, as shown in Example 1-36, from Chapter 13. Here we’ve got three ellipses and a path composed inside a canvas that is hosted inside a stack panel with a text block that, when scaled via the LayoutTransform prop- erty on the button, produces Figure 1-20. Notice that there’s nothing special about the graphics primitives in XAML; they’re declared and integrated as content just like any of the other WPF elements we’ve discussed. The graphics and the transformation are integrated into the same presenta- tion stack as the rest of WPF, which is a bit of a difference for experienced User/GDI programmers. For a complete discussion of how graphics primitives, retained drawings, color, lines, brushes, and transformations happen in WPF, both declaratively and in code, and for an introduction to 3D and video, read Chapter 13. Example 1-36. Adding graphics to a Button Figure 1-20. A scaled button with a collection of graphics primitives 34 | Chapter 1: Hello, WPF 3D Graphics in WPF are not limited to 2D; Figure 1-21 shows an example of a figure that was defined using WPF’s 3D capabilities. For an introduction to 3D and how it integrates with your WPF applications, you’ll want to read Chapter 17. Documents and Printing The document support in WPF is about flowing all the different content types you’ve seen in the rest of this chapter, along with special text-specific content types, into a seamless whole, a small sample of which is shown in Figure 1-22. Figure 1-21. 3D plot of data (Color Plate 2) Figure 1-22. A flowing document Where Are We? | 35 The text-specific content support is provided with the flow document and related ele- ments that provide advanced typography; adaptive, flow-based layout; spellchecking; hyphenation; and more, as described in Chapter 14. In addition, the base of the flow document supports printing, as do the rest of the WPF visual elements, via the XML Paper Specification (XPS), as covered in Chapter 15. Where Are We? WPF applications have a great deal of power, at which this chapter can only hint. The base services of the application aren’t too surprising, but the support for page- based navigation and browser hosting certainly adds a new capability for Windows applications, further enhanced with .NET 2.0 ClickOnce support. Building your application is a matter of grouping controls in containers—either sin- gle content containers, like windows or buttons, or multiple content containers that provide layout capabilities, like the canvas and the grid. When bringing your controls together, you’ll want to populate them with data that’s synchronized with the in-memory home of the data, which is what data binding is for, and keep them pretty, which is what styles are for. If you want to declare data or styles in your XAML, you can do so using resources, which are just arbitrarily named objects that aren’t used to render the WPF UI directly. If no amount of data or style property settings makes you satisfied with the look of your control, you can replace it completely with control templates, which can com- prise other controls or graphics primitives. In addition, you can apply graphics operations, like rotating, scaling, or animation, to 2D or 3D graphics primitives or controls in WPF’s integrated way. These elements can further be gathered into doc- uments for viewing or printing. 36 Chapter 2CHAPTER 2 Applications and Settings 2 A WPF application is a Windows process in which you have an instance of the WPF Application object. The Application object provides lifetime services and integration with ClickOnce deployment. Between application sessions, you’ll want to be able to keep application and user settings in a way that integrates well with WPF applica- tions. All of these topics are the focus of this chapter. On the other hand, if you’re interested in XML Browser Applications (XBAPs)— applications hosted in the browser and deployed over the Web—read Chapter 11. Application Lifetime In the Windows sense, an “application” is an address space and at least one thread of execution (a.k.a. a “process”). In the WPF sense, an application is a singleton object that provides services for UI components and UI programmers in the creation and execution of a WPF program. More specifically, in WPF, an application is an instance of the Application class from the System.Windows namespace. Explicit Application Creation Example 2-1 shows code for creating an instance of the Application class. Example 2-1. Creating an application explicitly using System; using System.Windows; // the home of the Application class class Program { [STAThread] static void Main( ) { Application app = new System.Windows.Application(); Window1 window = new Window1( ); window.Show( ); app.Run(); } } Application Lifetime | 37 Here, we’re creating an application inside an STA thread,* creating a window and showing it, and then running the application. While the application is running, WPF processes Windows messages and routes events to WPF UI objects as necessary. When the Run method returns, messages have stopped being routed and generally don’t start again (unless you show a modal window after the Run method returns, but that’s not something you’ll usually do). During its lifetime, the application provides various services. Application Access One of the services the Application class provides is access to the current instance. Once an instance of the Application class is created,† it’s available via the Current staticproperty of the Application class. For example, the code in Example 2-1 is equivalent to the code in Example 2-2. Here, in the process’s entry point, we’re creating an application, creating and show- ing the main window, and then running the application. Creation of the Application object fills the static Application.Current property. Access to the current application is very handy in other parts of your program where you don’t create the application or when you let WPF create the application for you itself. * The “Single Threaded Apartment” (STA) was invented as part of the native Component Object Model (COM) to govern the serialization of incoming COM calls. All Microsoft presentation frameworks, native or managed, require that they be run on a thread initialized as an STA thread so that they can integrate with one another and with COM services (e.g., drag-and-drop). † WPF makes sure that, at most, one Application object is created per application domain. For a discussion of .NET application domains, I recommend Essential .NET, by Don Box with Chris Sells (Addison-Wesley Professional). Example 2-2. Implicitly filling in the Application.Current property using System; using System.Windows; class Program { [STAThread] static void Main( ) { // Fills in Application.Current Application app = new System.Windows.Application(); Window1 window = new Window1( ); window.Show( ); Application.Current.Run(); // same as app.Run( ) } } 38 | Chapter 2: Applications and Settings Implicit Application Creation Because a Main method that creates and runs an application is pretty darn common, WPF can provide the process’s entry point for you. WPF projects generally desig- nate one XAML file that defines the application. For example, if we had defined our application in a XAML file with code behind, it would look like Example 2-3. Notice that Example 2-3 is defining a custom application class in this code (ImplicitAppSample.App) that derives from the Application class. In the OnStartup override, we’re only creating a window and showing it, assuming WPF is going to create the Main for us that creates the instance of the App class and calls the Run method (which calls the OnStartup method). The way that WPF knows which XAML file contains the definition of the Application class is that the Build Action is set to ApplicationDefinition, as shown in Figure 2-1. The ApplicationDefinition Build Action lets WPF know which class is our applica- tion and hooks it up appropriately in a Main method it generates for us, which saves us from writing several lines of boilerplate code. Example 2-3. Declaring an application in XAML // App.xaml.cs using System; using System.Windows; namespace ImplicitAppSample { public partial class App : System.Windows.Application { protected override void OnStartup(StartupEventArgs e) { // let the base class have a crack base.OnStartup(e); // WPF itself is providing the Main that creates an // Application and calls the Run method; all we have // to do is create a window and show it Window1 window = new Window1( ); window.Show( ); } } } Application Lifetime | 39 For msbuild aficionados, the standard XAML Build Action setting of Page looks like this in the .csproj file: ... App.xaml Code ... ... When we switch the Build Action to ApplicationDefinition, it looks like this: ... App.xaml Code Figure 2-1. Setting the Build Action for the application definition XAML file 40 | Chapter 2: Applications and Settings ... ... This setting causes the WPF build tasks to generate the following code: namespace ImplicitAppSample { public partial class App : Application { [System.STAThreadAttribute( )] [DebuggerNonUserCodeAttribute( )] public static void Main() { ImplicitAppSample.App app = new ImplicitAppSample.App(); app.Run(); } } } Except for the debugger attribute (which stops Visual Studio from stepping into this method when debugging), this is equivalent to what we were writing by hand a few code samples ago. If our window class is defined in a XAML file itself (as most likely it will be), we can save ourselves from overriding the OnStartup method by setting the StartupUri prop- erty in the application’s XAML file (see Example 2-4). The combination of setting the Build Action of the application’s XAML file to ApplicationDefinition and the StartupUri property provides the following features: • Creating an instance of the Application object and setting it as the value of the Application.Current property • Creating and showing an instance of the UI defined in the XAML designated in the StartupUri property • Setting the Application object’s MainWindow property • Calling the Application object’s Run method, keeping the application running until the main window is closed This set of features makes more sense when we get a handle on what the “main win- dow” is. Example 2-4. Setting the StartupUri on the application Application Lifetime | 41 Top-Level Windows A top-level window is a window that is not contained within or owned by another window (window ownership is discussed in more detail later). A WPF application’s main window is the top-level window that is set in the MainWindow property of the Application object. This property is set by default when the first instance of the Window class is created and the Application.Current property is set. In other words, by default, the main window is the top-level window that’s created first after the application itself has been created. If you like, you can override this default by set- ting the MainWindow property manually. In addition to the main window, the Application class provides a list of top-level windows from the Windows property. This is useful if you’d like to implement a Win- dow menu, like the one in Figure 2-2. To implement the Window menu, we first start with a MenuItem element: Figure 2-2. Managing the top-level windows exposed by Application 42 | Chapter 2: Applications and Settings MenuItem is a HeaderedItemControl (as described in Chapter 5), which means that it has header content that we’ll use to hold the name of the menu item (“Window”), and subcontent that we’ll use to hold the menu items for each top-level window. Notice the use of a dummy subitem to start with. Without this dummy item, you won’t be able to get notification that the user has asked to show the menu items (whether via mouse or via keyboard). To populate the Window menu, we’ll handle the menu item’s SubmenuOpened event: public partial class Window1 : Window { ... public Window1( ) { InitializeComponent( ); windowMenu.SubmenuOpened += windowMenu_SubmenuOpened; } void windowMenu_SubmenuOpened(object sender, RoutedEventArgs e) { windowMenu.Items.Clear( ); foreach (Window window in Application.Current.Windows) { MenuItem item = new MenuItem( ); item.Header = window.Title; item.Click += windowMenuItem_Click; item.Tag = window; item.IsChecked = window.IsActive; windowMenu.Items.Add(item); } } void windowMenuItem_Click(object sender, RoutedEventArgs e) { Window window = (Window)((MenuItem)sender).Tag; window.Activate( ); } } When the SubmenuOpened event is triggered, we use the Application object’s Windows property to get a list of each top-level Window, creating a corresponding MenuItem for each Window. For those of you already steeped in data binding and data templates who are wondering why we’re populating the Window menu manu- ally, it’s because the WindowCollection class that the Windows property returns doesn’t provide notifications when it changes, so once the Window menu is populated initially, there’s no way to keep it up-to- date. Maybe next version... Application Lifetime | 43 Application Shutdown Modes Some applications work naturally with the idea of a single main window. For example, many applications (drawing programs, IDEs, Notepad, etc.) have a single top-level win- dow that controls the lifetime of the application itself (i.e., when the main window goes away, the application shuts down). On the other hand, some applications have multiple top-level windows or some other kind of lifetime control that’s independent of a single main window.* You can specify when your application shuts down by setting the appli- cation’s ShutdownMode property to one of the values of the ShutdownMode enumeration: namespace System.Windows { public enum ShutdownMode { OnLastWindowClose = 0, // default OnMainWindowClose = 1, OnExplicitShutdown = 2, } } The OnMainWindowClose value is useful when you’ve got a single top-level window, and the OnLastWindowClose value is useful for multiple top-level windows (and is the default). In either of these cases, in addition to the automatic application shutdown the ShutdownMode policy describes, an application can also be shut down manually by calling the Application object’s Shutdown method. However, in the case of OnExplicitShutdown, the only way to stop a WPF application is by calling Shutdown: public partial class Window1 : System.Windows.Window { ... void shutdownButton_Click(object sender, RoutedEventArgs e) { Application.Current.Shutdown(); } } You can change the shutdown mode in code whenever you like, or you can set it in the application definition XAML: Of course, there are a number of ways to shut down a Windows pro- cess. The Application.Shutdown method is a nice way of doing it by closing the top-level windows and returning from the Run method. This lets the windows involved get their Closing and Closed notifica- tions, although canceling the shutdown in the Closing event doesn’t actually stop the application shutdown process. * For example, if an Office application is serving OLE objects, closing the windows will not cause the process to stop until those OLE objects are no longer needed. 44 | Chapter 2: Applications and Settings Application Events You can best see the life cycle of a standard application in the set of events that it exposes:* • Startup • Activated • Deactivated • DispatcherUnhandledException • SessionEnding • Exit Startup event The Application object’s Startup event is fired when the application’s Run method is called, and it is a useful place to do application-wide initialization, including the handling of command-line arguments, which are passed in the StartupEventArgs: void App_Startup(object sender, StartupEventArgs e) { for (int i = 0; i != e.Args.Length; ++i) { // do something useful with each e.Args[i] ... } } Activated and Deactivated events The Activated event is called when one of the application’s top-level windows is acti- vated (e.g., via a mouse click or Alt-Tab). Deactivated is called when your application is active and another application’s top-level window is activated. These events are handy when you want to stop or start some interactive part of your application: void App_Activated(object sender, EventArgs e) { ResumeGame( ); } void App_Deactivated(object sender, EventArgs e) { PauseGame( ); } DispatcherUnhandledException event The application’s dispatcher is an object that routes events to the correct place, includ- ing unhandled exceptions. In the event that you’d like to handle an exception otherwise unhandled in your application—maybe to give the user a chance to save his current document and exit—you can handle the DispatcherUnhandledException event: * Navigation events aren’t listed here, but are discussed in Chapter 11. Application Lifetime | 45 void App_DispatcherUnhandledException( object sender, DispatcherUnhandledExceptionEventArgs e) { string err = "Oops: " + e.Exception.Message); MessageBox.Show(err, "Exception", MessageBoxButton.OK); // Only useful if you've got some way of guaranteeing that // your app can continue reliably in the face of an exception // without leaving this AppDomain in an unreliable state... //e.Handled = true; // stop exception from bringing down the app } The Exception property of the DispatcherUnhandledExceptionEventArgs event argu- ment is useful to communicate to your users what happened, whereas the Handled property is useful to stop the exception from actually bringing down the application (although this is a dangerous thing to do and can easily result in data loss). SessionEnding event The SessionEnding event is called when the Windows session itself is ending (e.g., in the event of a shutdown, logoff, or restart): void App_SessionEnding(object sender, SessionEndingCancelEventArgs e) { if (MessageBox.Show( e.ReasonSessionEnding.ToString( ), "Session Ending", MessageBoxButton.OKCancel) == MessageBoxResult.Cancel) { e.Cancel = true; // stop the session from ending } } The ReasonSessionEnding property of the SessionEndingCancelEventArgs event argu- ment is one value in the ReasonSessionEnding enumeration: namespace System.Windows { public enum ReasonSessionEnding { Logoff = 0, Shutdown = 1, } } The Cancel property is useful if you’d like to stop the session from ending, although this is considered rude, and more progressive versions of Windows (like Vista) may not let you change its decision to end a session at all. Exit event The Exit event is called when the application is actually exiting, whether the last window has gone away, the Application.Shutdown method is called, or the session is ending. One of the overloads of the Shutdown method allows the programmer to pass an integer exit code, which is ultimately exposed by the process for use by your 46 | Chapter 2: Applications and Settings favorite Win32 process examination APIs. By default, this value is zero, but you can observe or override it in the handlers for this event: void App_Exit(object sender, ExitEventArgs e) { e.ApplicationExitCode = 452; // keep 'em guessing... } Application Instancing While we’ve just been talking about the lifetime of an application, things get a bit more interesting when you take into account that multiple instances of a single appli- cation can be running at any one time simply because the user can double-click on the same EXE multiple times. In fact, the default behavior in Windows and WPF does nothing to hamper or support multiple instances of the same application. For example, if we double-click on the AppWindowsSample.exe from Figure 2-2 more than once, we get more than one instance, as Figure 2-3 shows. Figure 2-3. Multiple instance applications Application Lifetime | 47 In Figure 2-3, we’ve got several top-level windows, some associated with each of three instances of the application. Sometimes more than one instance of a single application is a good thing. However, sometimes it just confuses users. For example, in Figure 2-3, even though we’ve got a Window menu, only the windows associated with each instance of the application are shown, which can be confusing as heck to the poor user faced with such an application. Single instance applications If you’d like your application to be single instance, it’s easy to detect an existing instance and shut down any subsequent instances (see Example 2-5). In Example 2-5, the key is to access a Windows mutex with a session-wide unique name so that we can tell whether it was already created by an initial instance or whether we’re the initial instance. The mutex name we’re using is one we pick to be sufficiently unique for our needs. Once the mutex has been created, it’ll live for the life of the WPF Application object itself, which will live for the life of the process, so if we’re not the first one to create the mutex, we shut down our application, causing our process to exit. However, it’s at this point that we realize that single instance detection isn’t the only feature we want; we also want the following: • Passing command-line arguments to the initial instance (e.g., in case a subse- quent instance was passed a filename that the user would like opened) • Activating the main window of the initial instance • Dealing properly with multiple users logging into a single computer (even the same user logged in multiple times), giving each login an instance of the application These are services that are not trivial to implement and we’d really love it if .NET provided this functionality for us. The good news is that it does. The bad news is that it’s provided only as part of the .NET 2.0 Visual Basicsupport for Windows Forms. Example 2-5. Very simple existing instance detection public partial class App : System.Windows.Application { Mutex mutex; protected override void OnStartup(StartupEventArgs e) { base.OnStartup(e); // Check for existing instance string mutexName = "MyCompanyName.MyAppName"; bool createdNew; mutex = new Mutex(true, mutexName, out createdNew); // If there is an existing instance, shut down this one if( !createdNew ) { Shutdown( ); } } } 48 | Chapter 2: Applications and Settings If you’d like to take advantage of robust single instance management, you have to load the Microsoft.VisualBasic assembly, and you have an interesting integration challenge ahead of you, as both Visual Basic’s support for single instance manage- ment and WPF want to be “the” application. However, it is possible and you get to leverage Other People’s Code (OPC), of which I’m a big fan, especially when the “other people” are a multibillion-dollar corporation with a record of framework maintenance and upgrades.* For an example of how to integrate single instance detection from Visual Basic into WPF, check out the “Single Instance Detection” sample in the Windows Platform SDK.† Other Application Services In addition to what we’ve already discussed, the Application class provides access to app-level resources and navigation services. Chapter 12 discusses resources and Chapter 11 discusses navigation. The other major service that WPF applications sup- port is ClickOnce deployment, which we’ll discuss right now. Application Deployment For the purposes of demonstration, let’s build something vital for procrastinators the world over: an application to generate excuses. The application was started with the “Windows Application (WPF)” project template in Visual Studio 2005 and was implemented with some very simple code. When you run it, it gives you an excuse from its vast database, as shown in Figure 2-4. * Whether Microsoft has a “good” record of framework maintenance and updates, I’ll leave to you to decide.... † Available online at http://msdn2.microsoft.com/en-us/library/ms771662.aspx (http://tinysells.com/85). Figure 2-4. A WPF excuse-generation application Application Deployment | 49 Simple Publishing For anyone to use this wonderful application, it must be published. The simplest way to publish your WPF application is by right-clicking on the project in the Solu- tion Explorer and choosing the Publish option, which will bring up the first page of the Publish Wizard (shown in Figure 2-5). Figure 2-5 asks you to choose where you’d like to deploy your application, including to the disk, to a network share, to an FTP server, or to a web site. By default, the Publish Wizard will assume you want to publish to the Publish subdirectory of your project directory. Clicking the Next button yields Figure 2-6. Because we’ve chosen to publish to something besides a web site, the Publish Wiz- ard wants to know how users will access your published application—in other words, from a URL, from a UNC path, or from some optical media. (If you choose to publish to a web site, the only way to access the application is from a URL, so it won’t bother to ask.) We’d like to test web deployment, so we pick that option and leave the default URL alone. Clicking Next yields Figure 2-7. For WPF applications, Figure 2-7 lets us choose whether we’d like this application to be made available online (when the computer is able to connect to the application’s URL) as well as offline (when the computer can’t connect to the URL), or whether you’d like the application to be only available online. These two options corre- spond to the ClickOnce terms locally installed and online only, respectively. Figure 2-5. Publish Wizard publish location 50 | Chapter 2: Applications and Settings Figure 2-6. Publish Wizard installation options Figure 2-7. Install mode in the Publish Wizard Application Deployment | 51 The job of the Publish Wizard is to bundle up the files needed to deploy an appli- cation using ClickOnce, including the manifest files that ClickOnce needs to deploy the application to a client machine after it’s been published. This example assumes a standalone application, which provides its own host window. WPF also supports the XBAP application type, which is an application composed of one or more pages and hosted in Internet Explorer 6+. You can also publish an XBAP via ClickOnce from within Visual Studio, but the options are different. Chapter 11 discusses XBAP creation, publication, and deployment details. Leaving the default “online or offline” option and clicking the Finish button yields Figure 2-8. Figure 2-8 reminds us what we get with a locally installed ClickOnce application (i.e., the application will appear in the Start menu and in the Add or Remove Programs Control Panel). Clicking Finish causes Visual Studio to publish the application to the filesystem, including a publish.htm file that you can use to test deployment. If you happen to have an IIS application set up in the same folder to which Visual Studio publishes, it will launch the publish.htm file for you, as shown in Figure 2-9. For simple needs, this is the complete experience for publishing a WPF ClickOnce locally installed application. Figure 2-8. A summary of the chosen Publish options 52 | Chapter 2: Applications and Settings The User Experience The user experience for running a ClickOnce locally installed application begins with a web page, such as the one shown in Figure 2-9, that includes a link to install the ClickOnce application. Clicking the link for the first time shows a download progress dialog similar to Figure 2-10. Once the metadata file describing the application deployment settings has been downloaded (this file is called the application manifest), it will be checked for a Figure 2-9. The Visual Studio-generated HTML file for testing ClickOnce applications Figure 2-10. Progress dialog for checking the application manifest Application Deployment | 53 certificate, which is extra information attached to the application that identifies a val- idated publisher name. ClickOnce requires all published applications to be signed, so Visual Studio will generate a certificate file for you as part of the initial publica- tion process if you haven’t already provided one. If the certificate used to sign the application manifest identifies a publisher that is already approved to install the application on the user’s machine (such as from a pre- vious version or a IT-administered group policy), the application will be run without further ado, as shown at the beginning of this chapter in Figure 2-4. If, on the other hand, the publisher’s certificate cannot be verified or is not yet trusted to run the application in question, a dialog similar to Figure 2-11 will be presented. Figure 2-11 displays the name of the application, the source of the application, and the publisher of the application according to the certificate (or “Unknown Pub- lisher” if the certificate could not be verified). It also lists a summary of the reasons this dialog is being shown, along with a link to more detailed warning information. However, such information will likely be ignored by the user choosing between the Install and Don’t Install buttons, from which the user will choose depending on the level of trust she has for the publisher she sees in the Security Warning dialog. If the user chooses Don’t Install, no application code will be downloaded or exe- cuted. If she chooses Install, the application is downloaded, added to the Start menu, and added to the Add or Remove Programs Control Panel, all under the umbrella of the progress dialog shown in Figure 2-12, after which the application is executed. Figure 2-11. The Application Install dialog with an unknown publisher 54 | Chapter 2: Applications and Settings Subsequent runs of the same version of the application, as launched from either a web site or the Start menu, will not ask for any additional user input (although they may show a dialog if checking for updates), but will launch the installed appli- cation directly. WPF ClickOnce Specifics There are a great number of additional details to ClickOnce application deployment, including security considerations, command-line handling, updating and rollback, prerequisite installation, access to external information sources, and certificate man- agement, just to name a few. All of these details are beyond the scope of this book and are covered in great detail by other sources.* However, following are some specif- ics to standalone and XBAP ClickOnce deployment you might like to see all in one place. Standalone WPF applications deployed using ClickOnce: • Can implement the main window with Window or NavigationWindow (although only the former has a project template in Visual Studio—the “Windows Applica- tion [WPF]” template) • Can be online-only or online/offline • If installed online/offline, can integrate with the Start menu, and can be rolled back and uninstalled • Must set “full trust” in the project’s Security settings (the Window class demands this) Figure 2-12. Progress dialog for installing a locally installed ClickOnce application * The SDK does a pretty good job, as does Smart Client Deployment with ClickOnce: Deploying Windows Forms Applications with ClickOnce, by Brian Noyes (Addison-Wesley Professional). Settings | 55 XBAP applications deployed using ClickOnce: • Provide their content with one or more Page objects to be hosted in the browser • Must be online-only to deploy with ClickOnce • There can be no “Security Warning” dialog, so must not attempt to elevate per- missions beyond what is provided already on the client’s machine • No custom pop-up windows are allowed (e.g., no dialogs); can use standard page navigation, page functions, and message boxes instead • Designated as XBAP by setting the HostInBrowser property to True in the project file (will be set by the “XAML Browser Application (WPF)” project template in Visual Studio) For the details of navigation-based applications and XBAP browser hosting and deployment, read Chapter 11. Settings WPF applications gain access to all the same application and user setting options that any other .NET application can use (e.g., the Registry, .config files, special fold- ers, isolated storage, etc.). Designing Settings The preferred settings mechanism for WPF applications is the one provided by .NET 2.0 and Visual Studio 2005: the ApplicationSettingsBase class from the System.Configuration namespace with the built-in designer. To access the set- tings for your application, click on the Settings tab in your project settings. This will bring up the Settings Designer shown in Figure 2-13. Here we’ve defined two settings: a user setting of type System.String, called LastExcuse; and an application setting of type System.Boolean, called ExcludeAnimalExcuses with a default value of True. These two settings will be loaded automatically when I run my application, pulled from the application’s configuration file (named MyApplication.exe. config) and the user settings file saved from the application’s last session. The Settings Designer manages a settings file and generates a class that allows you to program against the settings. For instance, our settings example will result in the class in Example 2-6 being generated (roughly). 56 | Chapter 2: Applications and Settings Figure 2-13. The Settings Designer Example 2-6. The Settings Designer-generated class using namespace System.Configuration; namespace excusegen.Properties { sealed partial class Settings : ApplicationSettingsBase { static Settings defaultInstance = ((Settings)(ApplicationSettingsBase.Synchronized(new Settings( )))); public static Settings Default { get { return defaultInstance; } } [UserScopedSettingAttribute()] [DefaultSettingValueAttribute("")] public string LastExcuse { get { return ((string)(this["LastExcuse"])); } set { this["LastExcuse"] = value; } } [ApplicationScopedSettingAttribute()] [DefaultSettingValueAttribute("True")] public bool ExcludeAnimalExcuses { Settings | 57 There are several interesting things to notice about Example 2-6. The first is the defaultInstance member, which is initialized with an instance of the generated Settings class that’s been synchronized to allow for safe multithreaded access. Sec- ond, notice that this defaultInstance member is staticand exposed from the Default staticproperty, whichmakes it very easy to get to our settings, as we’ll soon see. Finally, notice the two properties exposed from the Settings class, one property for each of our settings in the Settings Designer. You can see that the mode of each prop- erty, user versus application, the default value, and the type all match. Further, although a user setting is read-write (it has a getter and a setter), because it can change during an application session, the application setting is read-only (it has only a getter). The implementations of the properties are just type-safe wrappers around calls to the ApplicationSettingsBase base class, which does the work of reading and writing your settings to the associated settings storage. Using Settings With these typed properties in place and the Default staticproperty to expose an instance of our generated Settings class, usage is no different from any other CLR object, as you can see in Example 2-7. get { return ((bool)(this["ExcludeAnimalExcuses"])); } } } } Example 2-7. Using the Settings Designer-generated class public partial class Window1 : Window { string[] excuses = {...}; public Window1( ) { InitializeComponent( ); this.newExcuseButton.Click += newExcuseButton_Click; // If there is no "last excuse," show a random excuse if( string.IsNullOrEmpty(Properties.Settings.Default.LastExcuse) ) { ShowNextExcuse( ); } // Show the excuse from the last session else { excuseTextBlock.Text = Properties.Settings.Default.LastExcuse; } } void newExcuseButton_Click(object sender, RoutedEventArgs e) { ShowNextExcuse( ); } Example 2-6. The Settings Designer-generated class (continued) 58 | Chapter 2: Applications and Settings In Example 2-7, we’re using the LastExcuse user setting to restore the last excuse the user saw when running the application previously, changing it each time a new excuse is generated. The ExcludeAnimalExcuses application setting is checked to exclude animal-based excuses, but it is never set.* To store user settings that change during an application’s session, we’re calling the Save method on the Settings object from the ApplicationSettingsBase base class. This class does the magic of not only keeping the settings in memory and notifying you when a setting changes (if you choose to care), but also automatically loading the settings when the application is loaded, saving on demand. To help with the loading and saving, the ApplicationSettingsBase uses a settings provider, which is a pluggable class that knows how to read/write application settings (e.g., from the local filesystem, from the Registry, from a network server, etc.). The only settings provider that comes out of the box in .NET 2.0 is the one that writes to disk in a way that’s safe to use from even partial trust applications (like an XBAP), but it’s not hard to plug in your own settings provider if you need other behavior.† Random rnd = new Random( ); void ShowNextExcuse( ) { // Pick a random excuse, saving it for the next session // and checking for animals do { Properties.Settings.Default.LastExcuse = excuses[rnd.Next(excuses.Length - 1)]; } while( Properties.Settings.Default.ExcludeAnimalExcuses && HasAnimal(Properties.Settings.Default.LastExcuse) ); // Show the current excuse excuseTextBlock.Text = Properties.Settings.Default.LastExcuse; } bool HasAnimal(string excuse) {...} protected override void OnClosed(EventArgs e) { base.OnClosed(e); // Save user settings between sessions Properties.Settings.Default.Save(); } } * There is no configuration API to set an application setting. † The SDK comes with custom settings provider samples that use a web service and the Registry. I didn’t like the one based on the Registry, so I updated it and wrote a little article about the experience of writing and using a custom settings provider. It’s available at http://www.sellsbrothers.com/writing/default. aspx?content=dotnet2customsettingsprovider.htm (http://tinysells.com/86). Example 2-7. Using the Settings Designer-generated class (continued) Settings | 59 Integrating Settings with WPF None of the basics of the ApplicationSettingsBase-inspired support for settings, or any of the other mechanisms for doing settings in .NET, is specific to WPF. However, because the ApplicationSettingsBase class supports data change notifications (specifi- cally, it implements INotifyPropertyChanged), we can bind to settings data just like any other data (for the details of data binding, see Chapters 6 and 7). For example, instead of manually keeping the TextBlock that shows the excuse up-to-date, we can just bind the Text property to the LastExcuse property, as shown in Example 2-8. Example 2-8 shows a bit of an advanced use of the binding syntax, but basically it says that we’re binding the Text property of the TextBlock to the LastExcuse property of the excusegen.Properties.Settings.Default object. As the LastExcuse property changes, so does the Text property, so we no longer need to keep the Text property manually up-to-date; all we need to do is manage the LastExcuse property and the Text property will follow. For example: Random rnd = new Random( ); void ShowNextExcuse( ) { // Pick a random excuse, saving it for the next session // and checking for animals do { // This updates the Text property on the TextBlock, too Properties.Settings.Default.LastExcuse = excuses[rnd.Next(excuses.Length - 1)]; } while( Properties.Settings.Default.ExcludeAnimalExcuses && HasAnimal(Properties.Settings.Default.LastExcuse) ); // No longer any need to manually update the TextBlock //excuseTextBlock.Text = Properties.Settings.Default.LastExcuse; } The ability to use settings to drive a WPF UI makes the new .NET 2.0 ApplicationSettingsBase and Settings Designer the preferred means for managing settings in a WPF application. Example 2-8. Data binding to a settings class ... ... 60 | Chapter 2: Applications and Settings Where Are We? In WPF, the application contains an instance of the Application object. This object provides management services that let you control the lifetime of your application, as well as resource management and navigation, covered in Chapter 12, Appendix C, and Chapter 11, respectively. In this chapter, we also discussed deploying standalone applications using ClickOnce. (XBAP deployment can be found in Chapter 11.) Finally, to manage user and application settings between application sessions, we briefly dis- cussed the ApplicationSettingsBase-related settings services provided by .NET 2.0 and Visual Studio 2005. 61 Chapter 3 CHAPTER 3 Layout3 WPF provides a powerful and flexible array of tools for controlling the layout of the user interface. These tools enable applications to present information to users in a clear and logical way. There is a fine line between giving developers or designers enough control over the user interface’s layout, and leaving them to do all the work. A good layout system should be able to automate common scenarios such as resizing, scaling, and adapta- tion to localization, but should allow manual intervention where necessary. In this chapter, we will look at how WPF’s layout system helps fulfill these goals. Layout Basics WPF provides a set of panels—special-purpose user interface elements whose job is to arrange the elements they contain. Each individual panel type offers a straightforward and easily understood layout mechanism. As with all WPF elements, layout objects can be composed in any number of different ways, so although each individual panel type is fairly simple, the flexible way in which they can be combined makes for a very powerful layout system. And you can even create your own layout element types should the built-in ones not meet your needs. Table 3-1 describes the main panel types built into WPF.* Whichever panel you use, the same basicrule always applies: an element’s position is always determined by the containing panel. Most panels also manage the size of their children. * A frequently asked question is “why do some of these type names end in ‘Panel’ when some do not? The naming seems to be inconsistent.” The pattern appears to be that the names should be, unambiguously, nouns. Stack, Wrap, and Dock can all be used as verbs, which is why “Panel” is appended. Grid and Canvas are both nouns, so they don’t get “Panel” tacked on the end. 62 | Chapter 3: Layout By default, panels have no appearance of their own, the only visible effect of their presence being how they size and position their chil- dren. However, they can be made visible by setting their Background property. We’ll start with one of the most basic panels, StackPanel. StackPanel StackPanel is a very simple panel that arranges its children in a row or a column. You will not normally use StackPanel to lay out your whole user interface. It is most use- ful for arranging small subsections. Example 3-1 shows how to build a simple search user interface. Figure 3-1 shows the results. As you can see, the UI elements have simply been stacked vertically one after another. This example used the Margin property to space the elements out a little. Most elements use a single number, indicating a uniform margin all around. The Button uses a pair of numbers to specify different vertical and Table 3-1. Main panel types Panel type Usage StackPanel Lays children out in a vertical or horizontal stack; extremely simple, useful for managing small-scale aspects of layout. WrapPanel Lays children out from left to right, moving onto a new line each time it fills the available width. DockPanel Allocates an entire edge of the panel area to each child; useful for defining the rough layout of simple applications at a coarse scale. Grid Arranges children within a grid; useful for aligning items without resorting to fixed sizes and posi- tions. The most powerful of the built-in panels. Canvas Performs no layout logic—puts children where you tell it to; allows you to take complete control of the layout process. UniformGrid Arranges children in a grid where every cell is the same size. Example 3-1. StackPanel search layout Look for: Filtered by: Search in titles only Match related words Search in previous results Highlight search hits (in topics) StackPanel | 63 horizontal margins. This is one of several standard layout properties available on all WPF elements, which are all described in the “Common Layout Properties” section, later in this chapter. Many of the examples in this book represent typical snippets of XAML, rather than complete self-contained programs. You can down- load runnable versions of the examples from the book’s web site at http://sellsbrothers.com/writing/wpfbook. If you would prefer to type in the examples, you can do that using the XamlPad tool that ships with the Windows SDK, but because the examples are only snippets, you will need to host them in a suitable root element such as a Page. There is one problem with this layout: the Search button is much wider than you would normally expect a button to look. The default behavior of a vertical StackPanel is to make all of the controls the same width as the panel. Likewise, a horizontal StackPanel will make all of the controls the same height. For the ComboBox controls, this is exactly what we want. For the TextBlock and CheckBox controls, it doesn’t show that the controls have been stretched to be as wide as the panel, because they look only as wide as their text makes them look. However, a Button’s visuals always fill its entire logical width, which is why the button in Figure 3-1 is unusually wide. (See the upcoming “Fixed Size Versus Size to Content” sidebar for more details on how this process works.) When an element has been given a fixed amount of space that is greater than required by its content, the way in which the extra space gets used is determined by the HorizontalAlignment and VerticalAlignment properties. We can prevent the button from being stretched across the panel’s whole width by setting its HorizontalAlignment property to Left: Figure 3-1. Search StackPanel with Margin 64 | Chapter 3: Layout HorizontalAlignment determines an element’s horizontal position and width in situa- tions where the containing panel gives it more space than it needs. The default is Stretch, meaning that if more space is available than the child requires, it will be stretched to fill that space. The alternatives—Left, Right, and Center—do not attempt to stretch the element; these determine where the element will be placed within the excess space, allowing the element to use its natural width. Here we are using Left, meaning that the control will have its preferred width, and will be aligned to the left of the available space (see Figure 3-2). Fixed Size Versus Size to Content WPF can tackle the layout of an element in one of two ways. The strategy is determined by whether or not the amount of space available is fixed. For example, if the user resizes a window, the size of the window’s content is whatever the user wants it to be. From the point of view of the layout system, the size is fixed—it is imposed on the lay- out system by the user. In such a case, the job of the layout system is to arrange the contents as best it can in the space available. On the other hand, if the available space is not predetermined, WPF uses a “size to con- tent” approach, where the size is not dictated upfront, but is instead calculated based on the content to be displayed. The most straightforward example of this is when a Window whose SizeToContent property is set to WidthAndHeight is first displayed— although the user may resize the window after it opens, its initial size is determined by measuring the content. A mixture of these two styles may be used—one in each direction. For example, if a window’s SizeToContent is set to Height, the window height will be determined by measuring the content, but the width will be fixed, as specified by the Width property. A panel subject to fixed layout does not necessarily pass this layout style on to its chil- dren. For example, suppose the user resizes a window that contains a vertical StackPanel. The window will impose a fixed size on the StackPanel, but although the StackPanel will pass the fixed width on to its children, it will use the size to content approach to determine each element’s height. The converse can also apply—unconstrained elements may constrain their children. For example, if a vertical StackPanel is unconstrained (i.e., its parent asks it to size to content), it must choose a width for itself. It does this by measuring each child’s pre- ferred width, but it then picks the width of the widest child. This is then passed on as a fixed width to every child in the panel. (This is exactly what’s happening in Figure 3-1—the panel has made itself wide enough for the widest child, and has fixed every child to that width. It might not look that way with the checkboxes, as they look only as wide as their text. However, if they acquired the focus, the focus rectangle would illustrate their full width.) WrapPanel | 65 The preceding example used the default vertical orientation. StackPanel also supports horizontal layout. Example 3-2 shows a StackPanel with its Orientation property set to Horizontal. These elements will be arranged in a horizontal line, as shown in Figure 3-3. StackPanel is not very smart when it runs out of space. If you give it more elements than will fit, it will just truncate the content. However, its close relative, the WrapPanel, copes rather better. WrapPanel WrapPanel works just like a StackPanel until it runs out of space. If you provide a hor- izontal WrapPanel with more children than will fit in the available width, it will arrange its content in a way similar to how a word processor lays out words on a line. It puts the children in a row from left to right until it runs out of space, at which point it starts on the next line. WrapPanel is very simple to use. Just as with a StackPanel, you add a sequence of chil- dren, as Example 3-3 shows. Figure 3-2. Search panel with unstretched Button Example 3-2. Horizontal StackPanel layout This is some text Check it out More text Figure 3-3. Horizontal StackPanel layout 66 | Chapter 3: Layout As Figure 3-4 shows, the items are arranged from left to right. As you can see from the panel’s filled-in background, it is not wide enough to accommodate all the items, so the last three have been wrapped onto the next line. WrapPanel also offers an Orientation property. Setting this to Vertical will arrange the children in a sequence of vertical stacks, a layout style very similar to Windows Explorer’s “List” view. WrapPanel and StackPanel really are useful only for small-scale layout. You will need to use a more powerful panel to define the overall layout of your application, such as DockPanel. DockPanel DockPanel is useful for describing the overall layout of a simple user interface. You can carve up the basic structure of your window using a DockPanel, and then use the other panels to manage the details. A DockPanel arranges each child element so that it fills a particular edge of the panel. If multiple children are docked to the same edge, they simply stack up against that edge in order. By default, the final child fills any remaining space not occupied by controls docked to the panel’s edges. Example 3-4 shows a simple DockPanel-based layout. Five buttons have been added to illustrate each option. Notice that four of them have a DockPanel.Dock attribute applied. This property is defined by DockPanel to allow elements inside a DockPanel to specify their position. DockPanel.Dock is an attachedproperty (as described in the upcoming sidebar, “Attached Properties and Layout”). Example 3-3. WrapPanel Figure 3-4. WrapPanel DockPanel | 67 Figure 3-5 shows how the UI built in Example 3-4 looks on-screen. Notice how the Top and Bottom buttons have filled the entire top and bottom edges of the window, and yet the Left and Right buttons do not fill their edges—the Top and Bottom but- tons have taken control of the corners. This is because Top and Bottom were added to the panel first. Example 3-4. Simple DockPanel layout Attached Properties and Layout Most WPF panels allow child elements to specify their layout requirements. For example, a child of a DockPanel needs to be able to specify to which edge it would like to dock. The obvious solution would be for a base class such as FrameworkElement to define a Dock property—all WPF user interface elements derive from FrameworkElement, so this would enable anything to specify its dock position. However, DockPanel is not the only panel type, so we would need to add properties for the benefit of other panels, too. This would add a lot of clutter. Worse, it would also be inflexible—what if you want to design a custom panel that implements some new layout mechanism? It might need to define new attributes for its children to use. Attached properties solve this problem. They allow one element to define properties that can be “attached” to some other element. DockPanel defines a Dock property that can be attached to any child. In XAML, the dotted attribute syntax (DockPanel.Dock) signifies that an attached property is being used. Example 3-4 uses this technique. See Appendix A for more detailed information about XAML and attached properties. Figure 3-5. Simple DockPanel layout 68 | Chapter 3: Layout If you swapped these over so that the Left and Right buttons came first in the markup, as shown in Example 3-5, they would fill their whole edges, including the corners, leaving the Top and Bottom buttons with just the remaining space. Figure 3-6 shows the results. Elements never overlap in a DockPanel, so each successive child only gets to use space not already used by the previous children. By default, the final child takes all of the remaining space, but if you would prefer to leave a blank space in the middle, you can set the LastChildFill attribute of the DockPanel to False. (It defaults to True.) The final child will dock to the left by default, leaving the center empty. For items docked to the top or bottom, DockPanel sets the width to fill the space available, but for the height, it sizes to content—as described in the earlier sidebar. Likewise, items docked to the left or right have their heights fixed to fill the available space, but size to content horizontally. In Figures 3-5 and 3-6, the buttons at the top and bottom are just tall enough to contain their text. Likewise, the buttons docked to the left and right are just wide enough to hold their text. If we put a lot more text into one of the buttons, it will try to expand in order to make the text fit. We can see in Figure 3-7 that the DockPanel is letting the button be exactly as wide as it wants to be. The DockPanel is good for creating the top-level structure of a basic user interface. For example, you could use it to position a menu and a toolbar at the top of the win- dow, with other content filling the remaining space. However, if you have lots of controls to arrange, it can be helpful to have table-like layout functionality. For this, we turn to the powerful Grid panel. Example 3-5. Docking Left and Right before Top and Bottom Figure 3-6. DockPanel layout, with Left and Right docked first Grid | 69 Grid Consider the document Properties dialog from Internet Explorer shown in Figure 3-8. Notice how the main area of the form is arranged as two columns. The column on the left contains labels, and the column in the middle contains information. Figure 3-7. DockPanel layout, with an unusually wide button Figure 3-8. Document Properties dialog 70 | Chapter 3: Layout Achieving this kind of layout with any of the panels we’ve looked at so far is diffi- cult, because they are not designed with two-dimensional alignment in mind. We could try to use nesting—Example 3-6 shows a vertical StackPanel with three rows, each with a horizontal StackPanel. The result, shown in Figure 3-9, is not what we want at all. Each row has been arranged independently, so we don’t get the two columns we were hoping for. The Grid panel solves this problem. Rather than working a single row or a single col- umn at a time, it aligns all elements into a grid that covers the whole area of the panel. This allows consistent positioning from one row to the next. Example 3-7 shows the same elements as Example 3-6, but arranged with a Grid rather than nested StackPanel elements. Example 3-6. Ineffective use of StackPanel Protocol: HyperText Transfer Protocol Type: HTML Document Connection: Not Encrypted Figure 3-9. Inappropriate use of StackPanel Example 3-7. Grid layout Grid | 71 The Grid needs to know how many columns and rows we require, and we indicate this by specifying a series of ColumnDefinition and RowDefinition elements at the start. This may seem rather verbose—a simple pair of properties on the Grid itself might seem like a simpler solution. However, you will often need to control the char- acteristics of each column and row independently, so in practice, it makes sense to have elements representing them. Notice that each element in the grid has its column and row specified explicitly using attached properties. This is mandatory—without these, everything ends up in col- umn 0, row 0. (Grid uses a zero-based numbering scheme, so 0,0 corresponds to the top-left corner.) Protocol: HyperText Transfer Protocol Type: HTML Document Connection: Not encrypted Grid, Element Order, and Z Order You might be wondering why the Grid doesn’t simply put items into the grid in the order in which they appear; this would remove the need for the Grid.Row and Grid.Column attached properties. However, grids do not necessarily have exactly one element per cell. Grid cells can be empty. If the grid’s children simply filled the cells in order, you would need to provide placeholders of some kind to indicate blank cells. But because ele- ments indicate their grid position, you can leave cells empty simply by providing no content for those cells. Elements may span multiple cells, by using the Grid.RowSpan and Grid.ColumnSpan attached properties. Cells can also contain multiple elements. In this case, the order in which the relevant elements are listed in the markup determines which appears “on top.” Elements that appear later in the document are drawn over those that appear earlier. The order in which overlapping elements are drawn is usually referred to as the Z order. This is because the x- and y-axes are traditionally the ones used for drawing on-screen, so the z-axis, representing the third dimension, “sticks out” of the screen. This makes it the logical axis to represent how overlapping elements stack up on top of one another. In general, panels that allow their children to overlap (e.g., Grid and Canvas) rely on the order in which elements appear in the XAML to determine the Z order. However, you can override this: the attached Panel.ZIndex property allows the Z order to be specified explicitly. Example 3-7. Grid layout (continued) 72 | Chapter 3: Layout Figure 3-10 shows the result of Example 3-7. This figure has lines showing the grid outline, because we enabled the ShowGridLines property. You would not normally do this on a finalized design—this feature is intended to make it easy to see how the Grid has divided up the available space. With grid lines displayed, it is clear that the Grid has made all the columns the same width, and all the rows the same height. What may not be obvious from Figure 3-10 is that each element has been given the full available cell space. It doesn’t show here because a TextBlock looks only as large as the text it shows. But the behavior is somewhat similar to a StackPanel—each element’s width is as wide as its containing column, and its height is that of its containing row. As always, you can use HorizontalAlignment and VerticalAlignment to determine what elements do with excess space. This default “one size fits all” behavior is useful when you want all the items in the grid to be the same size, but it’s not what we want here. It would make more sense for the column on the left to be wide enough to contain the labels, and for the col- umn on the right to be allocated the remaining space. Fortunately, the Grid provides a variety of options for managing column width and row height. Column Widths and Row Heights You configure the column widths and row heights in a Grid using the ColumnDefinition and RowDefinition elements. There are three sizing options: fixed, automatic, and proportional. Fixed sizing is the simplest to understand, but often requires the most effort to use, as you end up having to do all of the work yourself. You can specify the Width of a column or the Height of a row in device-independent pixels. (These are 1/96th of an inch. WPF’s coordinate system is described in Chapter 13.) Example 3-8 shows a modified version of the column definitions in Example 3-7, specifying a fixed width for the first column. Figure 3-10. Grid layout Example 3-8. Fixed column width ... Grid | 73 Figure 3-11 illustrates the main problem with using fixed column widths. If you make the column too narrow, the contents will simply be cropped. Fixed widths and heights may seem to be an attractive idea because they give you complete control, but in practice they tend to be inconvenient. If you change the text or the font, you will need to modify the sizes to match. You will need to be flexible on layout if you want your application to fit in with the system look and feel, because the default font is not the same on all versions of Windows. Localization of strings will also require the sizes to be changed. (See Chapter 12 for more information about localization.) So in prac- tice, fixed widths and heights are not what you will normally want to use. This is true not only with grids and text blocks. In general, you should try to avoid fixed sizes in WPF—the more you let the layout system do for you, the easier it is to adapt to local- ization, different screen sizes, and display orientations. The most appropriate sizing strategy for our label column will be automatic sizing. This tells the Grid to make the column wide enough to contain the widest element (i.e., to size to content). Example 3-9 shows a modified version of the column and row defini- tions from Example 3-7, specifying automatic width for the first column, and automatic heights for all of the rows. ... Figure 3-11. Fixed-width column truncation Example 3-9. Automatic width and height ... ... Example 3-8. Fixed column width (continued) 74 | Chapter 3: Layout This is not quite right yet—as you can see from Figure 3-12, the Grid has not left any space around the text, so the results seem rather cramped. The solution is exactly the same as it was for the StackPanel—we simply use the Margin property on the TextBlock elements in the Grid to indicate that we want some breathing room around the text. The Grid will honor this, giving us the layout we require. If the idea of adding a Margin attribute to every single element sounds tedious, don’t worry. We can give all of the TextBlock elements the same margin by defining a style. Styles are discussed in Chapter 8. Example 3-10 does this to set a horizontal margin of five device-independent pixels, and a vertical margin of three. As Figure 3-13 shows, this provides the better-spaced layout we require. The final mechanism for specifying width and height in a Grid is the proportional method. This is sometimes called “star” sizing because of the corresponding XAML syntax. If you set the width or height of a column or row to be *, this tells the Grid that it should fill all the space left over after any fixed and automatic items have taken their share. If you have multiple items set to *, the space is shared evenly among them. Figure 3-12. Automatic width and height Example 3-10. Applying a consistent margin with a style ... as before Figure 3-13. Using margins Grid | 75 The default value for column width and row height is *, so you have already seen the effect of this. As Figure 3-10 shows, when we don’t specify column widths or row heights, each cell ends up with exactly the same amount of space. The star syntax is a little more flexible than this. Rather than dividing up space evenly among all the rows or columns marked with a star, we can choose a propor- tional distribution. Consider the set of row definitions in Example 3-11. Here, the first row has been set to size automatically, and the other two rows both use proportional sizing. However, the middle row has been marked as 2*. This indi- cates that it wants to be given twice as much of the available space as the row marked with 1*. For example, if the grid’s total height was 350, and the first row’s automatic height came out as 50, this would leave 300 for the other rows. The sec- ond row’s height would be 200, and the third row’s height would be 100. Figure 3-14 shows how this grid looks for a couple of different heights; the filled-in background shows the size of the grid in each case. As you can see, the row with Auto height is the same in both cases. The two star-sized rows share out the remaining space, with the 2* row getting twice the height of the 1* row. The numbers before the * specify relative sizes, not absolute sizes. If you modified the preceding example to use 6* and 3* instead of 2* and 1*, the result would be exactly the same. It’s equivalent to saying that you want the rows to use six-ninths and three-ninths of the available space, instead of saying that you want them to use two-thirds and one-third—it’s just two ways of expressing the same ratio. Example 3-11. Mixing row height styles Figure 3-14. Proportional Grid sizing 76 | Chapter 3: Layout These numbers are floating point, so you can specify noninteger sizes such as 2.5*. And if you specify just * without a number, this is equivalent to 1*. If you are familiar with HTML, you may have been wondering whether you can use percentage sizes. You can’t, but the star mecha- nism lets you achieve similar effects. You may have noticed that for all three grid-sizing strategies, we used the Width and Height properties each time, although the property values looked quite different in each case. Width and Height are both of type GridLength. The GridLength type holds a number and a unit type. The number is stored as a Double and the unit type is repre- sented by the GridUnitType enumeration. For a fixed size, the unit type is Pixel. (As mentioned previously, in WPF pixel is really a device-independent unit, meaning 1/96th of an inch.) In XAML, this is indi- cated by providing just a number.* For automaticsizing, the unit type is Auto and no number is required. In XAML, this is indicated by the string "Auto". For propor- tional sizing, the unit type is Star. In XAML, this is indicated either by just * or a number and a star (e.g., 3.5*). Example 3-12 shows the C# equivalent of the row set- tings shown in XAML in Example 3-11. Spanning Multiple Rows and Columns Looking at the Properties dialog shown earlier in Figure 3-8, there is a feature we have left out. The dialog has two horizontal lines dividing the UI into three sections. How- ever, the aligned columns span the whole window, straddling these dividing lines. It would be inconvenient to try to achieve a layout like this with multiple grids. If you used one for each section of the window, you could keep the columns aligned in all the grids by using fixed column widths. As discussed earlier, use of fixed widths is inconvenient because it tends to require manual adjustment of the widths whenever * In XAML, you can also use the suffix in, cm,orpt to specify inches, centimeters, or points. These will all be converted to device-independent pixels, and the unit type will be Pixel. Sometimes these units don’t map neatly into pixels (e.g., a value of 1pt will be converted into 1.3333 pixels). Example 3-12. Setting row heights in code Grid g = new Grid( ); RowDefinition r = new RowDefinition( ); r.Height = new GridLength(0, GridUnitType.Auto); g.RowDefinitions.Add(r); r = new RowDefinition( ); r.Height = new GridLength(2, GridUnitType.Star); g.RowDefinitions.Add(r); r = new RowDefinition( ); r.Height = new GridLength(1, GridUnitType.Star); g.RowDefinitions.Add(r); Grid | 77 anything changes. With this layout, it becomes triply inconvenient—you would have to change all three grids every time anything changed. Fortunately, it is possible to add these dividing lines without splitting the UI into separate grids. The way to do this is to put the dividing lines into cells that span across all of the columns in the grid. An element indicates to its parent Grid that it would like to span multiple columns by using the attached Grid.ColumnSpan property. Example 3-13 uses a single Grid to show three sets of properties. These sets are sepa- rated by thin Rectangle elements, using Grid.ColumnSpan to fill the whole width of the Grid. Because a single Grid is used for all three sections, the columns remain aligned across all three sections, as you can see in Figure 3-15. If we had used three separate grids with the leftmost column set to use automatic width, each would have chosen its own width, causing the righthand columns to be misaligned. Example 3-13. Using Grid.ColumnSpan Title: Information Overload Protocol: Unknown Protocol Type: Not available Connection: Not encrypted 78 | Chapter 3: Layout The Grid class also defines a Grid.RowSpan attached property. This works in exactly the same way as Grid.ColumnSpan, but vertically. You are free to use both Grid.RowSpan and Grid.ColumnSpan on the same element— any element may occupy as many grid cells as it likes. Also, note that you are free to put multiple overlapping items into each cell. Example 3-14 illustrates both of these techniques. It adds two Rectangle elements to color in areas of the grid. The first spans multiple rows, and the second spans both multiple rows and columns. Both Rectangle elements occupy cells in the Grid that are also occupied by text. Figure 3-16 shows the results. Note that, in the absence of a Panel.ZIndex property, the order in which the elements appear in the markup is crucial, as it determines the Z order for overlapping elements. In Example 3-14 the Rectangle elements were added before the TextBlock items whose cells they share. This means that the colored rectangles appear behind the text, rather than obscuring them. If the rectangles had been added at the end of the Grid, after the text, they would have been drawn over the text. Created: Not available Modified: Not available Figure 3-15. Dividing lines spanning multiple columns Example 3-14. Multiple items in a Grid cell Title: ...as before Example 3-13. Using Grid.ColumnSpan (continued) Grid | 79 This example illustrates why the Grid requires the row and column of each item to be specified explicitly, rather than being implied by the order of the elements. Cells can be shared by multiple elements. Elements can span multiple cells. This makes it impossible for the Grid to guess which element goes in which cell. Consistency Across Multiple Grids Although the row and column spanning features described in the preceding section often make it possible to arrange your UI as you need, it will not always be possible to put all of the information you wish to present into a single Grid element. For example, consider a scrollable Grid with headings.* You could just put headings and contents into a single Grid and then place that Grid in a ScrollViewer to make it scrollable, but this suffers from a problem, which Example 3-15 illustrates. Figure 3-16. Overlapping Grid items * The ListView control provides just such a thing, so you don’t necessarily have to build your own. However, it also entails certain interactive behaviors that you may not want in your application. For example, ListView requires you to use data binding, whereas the alternative presented here does not. Example 3-15. Grid in ScrollViewer 80 | Chapter 3: Layout Figure 3-17 shows the results. If you look at the righthand side, you can see that the scroll bar runs the entire height of the Grid, including the header line with the titles. This means that as soon as you scroll down, the headings will disappear. This is not particularly helpful. We could solve this by using two grids, one for the header and one for the main results area. Only the second grid would be placed inside a ScrollViewer. Figure 3-18 shows the results. Title Location Rank Figure 3-17. Grid in ScrollViewer Figure 3-18. Separate Grid for headers Example 3-15. Grid in ScrollViewer (continued) Grid | 81 The scroll bar is now applied just to the part that needs to be scrollable, but the alignment is all wrong. Each Grid has arranged its columns independently, so the headings no longer line up with the main contents. The Grid supports sharedsize groups to solve this problem. A shared size group is simply a named group of columns, all of which will have the same width, even if they are in different grids. You can use shared size groups either across multiple grids or within a single grid. We can use a shared size group to keep the headings Grid consistent with the scrollable contents Grid. Example 3-16 illustrates the use of shared size groups. Example 3-16. Shared size groups Title Location Rank 82 | Chapter 3: Layout In this example, the overall layout is defined by a DockPanel, using the attached Dock.Top property to position the header Grid at the top, and allowing the ScrollViewer to fill the remaining space. Shared size groups are identified by strings. Strings are prone to name collisions—it’s quite possible that two developers independently working on different parts of the user interface might end up choosing the same name for their shared size groups, inadvertently causing unrelated columns to have the same size. To avoid this prob- lem, Example 3-16 sets the Grid.IsSharedSizeScope attached property on the DockPanel. This indicates that the DockPanel is the common ancestor, and prevents the groups defined inside the DockPanel from being associated with any groups of the same name defined elsewhere in the UI. Grid.IsSharedSizeScope is not optional. If you do not specify a shared size scope, WPF will ignore your shared size groups. Having defined the scope of the names, using shared size groups is very straight- forward. We just apply the SharedSizeGroup attribute to the “Location” and “Rank” ColumnDefinition, and this ensures that the columns are sized consistently across the two grids. Figure 3-19 shows the results. 1 IanG on Tap The Internet 2 Example 3-16. Shared size groups (continued) Grid | 83 The ScrollViewer adds a scroll bar to the display, and this means that a small hack is required to get this layout to work correctly. This scroll bar takes away some space from the main Grid, making it slightly narrower than the header Grid. Remember that the “Title” column’s size is set to *, meaning that it should fill all available space. The ScrollViewer’s scroll bar eats into this space, making the “Title” column in the main Grid slightly narrower than the one in the header Grid, destroying the alignment. You might think that we could fix this by adding a shared size group for the “Title” column. Unfortunately, specifying a shared size group disables the * behavior—the column reverts to automatic sizing. The fix for this is to add an extra column to the header row. This row needs to be exactly the same width as the scroll bar added by the ScrollViewer. So we have added a fourth column, containing a FrameworkElement, with its Width set to the sys- tem scroll width metric in order to make sure that it is exactly the same width as a scroll bar. (We are using a DynamicResource reference to retrieve this system parameter. This technique is described in Chapter 12.) It’s unusual to use a FrameworkElement directly, but because we just need something that takes up space but has no appear- ance, it makes a good lightweight filler object. Its presence keeps all of the columns perfectly aligned across the two grids. The Grid is the most powerful of the built-in panels. You can get the Grid to do anything that DockPanel and StackPanel can do—those simpler ele- ments are provided for convenience. For nontrivial user interfaces, the Grid is likely to be the best choice for your top-level GUI layout, as well as being useful for detailed internal layout. UniformGrid Powerful though the Grid is, it’s occasionally a little cumbersome to use. There’s a simplified version worth knowing about, called UniformGrid. All its cells are the same size, so you don’t need to provide collections of row and column descriptions—just set the Rows and Columns properties to indicate the size. In fact, you don’t even need to set these—by default, it creates rows and columns automatically. It always keeps the number of rows and columns equal to each other, adding as many as are required to make space for the children. Each cell contains just one child, so you do not need to add attached properties indicating which child belongs in which cell—you just add children. This means you can use something as simple as Example 3-17. Figure 3-19. Shared size groups 84 | Chapter 3: Layout This contains nine elements, so the UniformGrid will create three rows and three col- umns. Figure 3-20 shows the result. Canvas Occasionally, it can be necessary to take complete control of the precise positioning of every element. For example, when you want to build an image out of graphical elements, the positioning of the elements is dictated by the picture you are creating, not by any set of automated layout rules. For these scenarios, you can use a Canvas. Canvas is the simplest of the panels. It allows the location of child elements to be specified precisely relative to the edges of the canvas. The Canvas doesn’t really do any layout at all; it simply puts things where you tell it to. Also, Canvas will not size elements to fill the available space—all its children are sized to content. If you are accustomed to working with fixed layout systems such as those offered by Visual Basic6, MFC, and the most basicway of using Windows Forms, the Canvas will seem familiar and natural. However, it is strongly recommended that you avoid it unless you really need this absolute control. The automatic layout provided by the other pan- els will make your life much easier because they can adapt to changes in text and font. They also make it far simpler to produce resizable user interfaces. Moreover, localization tends to be much easier with resizable user interfaces, because different languages tend to produce strings with substantially different lengths. Don’t opt for the Canvas simply because it seems familiar. Example 3-17. UniformGrid Figure 3-20. UniformGrid Canvas | 85 When using a Canvas, you must specify the location of each child element. If you don’t, all of your elements will end up at the top-left corner. Canvas defines four attached properties for setting the position of child elements. Vertical position is set with either the Top or Bottom property, and horizontal position is determined by either the Left or Right property. Example 3-18 shows a Canvas containing two TextBlock elements. The first has been positioned relative to the top-left corner of the Canvas: the text will always appear 10 pixels in from the left and 20 pixels down from the top. (As always, these are device- independent pixels.) Figure 3-21 shows the result. The second text element is more interesting. It has been positioned relative to the bottom right of the form, which means that if the canvas gets resized, the element will move with that corner of the canvas. For example, if the Canvas were the main element of a window, the second TextBlock element would move with the bottom- right corner of the window if the user resized it. If you have used Windows Forms, you may be wondering whether set- ting both the Top and Bottom properties (or both Left and Right prop- erties) will cause the element to resize automatically when the containing canvas is resized. But unlike with anchoring in Windows Forms, this technique does not work. If you specify both Left and Right, or both Top and Bottom, one of the properties will simply be ignored. (Top takes precedence over Bottom, and Left takes precedence over Right.) Fortunately, it is easy to get this kind of behavior with a single-cell Grid and the Margin property. If you put an element into a grid with a margin of, say, “10,10,30,40”, its top-left corner will be at (10,10) rela- tive to the top left of the grid, its righthand side will always be 30 pixels from the right edge of the grid, and its bottom edge will always be 40 pixels from the bottom of the grid. This is another reason to prefer Grid over Canvas. Example 3-18. Positioning on a Canvas Hello world! Figure 3-21. Simple Canvas layout 86 | Chapter 3: Layout The main use for Canvas is to arrange drawings. If you employ graphical elements such as Ellipse and Path, which are discussed in Chapter 13, you will typically need precise control over their location, in which case the Canvas is ideal. When child elements are larger than their parent panel, most panels crop them, but the Canvas does not by default, allowing elements to be partially or entirely outside of its bounds. You can even use negative coordinates. The noncropping behavior is sometimes useful because it means you do not need to specify the size of the canvas— a zero-size canvas works perfectly well. However, if you want to clip the content, set ClipToBounds to True. The price you pay for the precise control offered by the Canvas is that it is inflexible. However, there is one common scenario in which you can mitigate this rigidity. If you’ve used a Canvas to arrange a drawing and you would like that drawing to be automatically resizable, you can use a Viewbox in conjunction with the Canvas. Viewbox The Viewbox element automatically scales its content to fill the space available. Strictly speaking, Viewbox is not a panel—it derives from Decorator. This means that unlike most panels, it can have only one child. However, its capability to adjust the size of its content in order to adapt to its surroundings makes it a useful layout tool. Figure 3-22 shows a window that doesn’t use a Viewbox but probably should. The window’s content is a Canvas containing a rather small drawing. Example 3-19 shows the markup. Figure 3-22. Canvas without Viewbox Example 3-19. Canvas without Viewbox Viewbox | 87 We can use a Viewbox to resize the content automatically. It will expand it to be large enough to fill the space, as shown in Figure 3-23. (If you’re wondering why the draw- ing doesn’t touch the edges of the window, it’s because the Canvas is slightly larger than the drawing it contains.) All we had to do to get this automaticresizing was wrap the Canvas element in a Viewbox element, as shown in Example 3-20. Figure 3-23. Canvas with Viewbox Example 3-20. Using Viewbox ...as before... Example 3-19. Canvas without Viewbox (continued) 88 | Chapter 3: Layout Notice how in Figure 3-23 the Canvas has been made tall enough to fill the window, but not wide enough. This is because by default, the Viewbox preserves the aspect ratio of its child. If you want, you can disable this so that it fills all the space, as Figure 3-24 shows. To enable this behavior we set the Stretch property. Its default value is Uniform.We can make the Viewbox stretch the Canvas to fill the whole space by setting the prop- erty to Fill, as Example 3-21 shows. You can also set the Stretch property to None to disable stretching. That might seem pointless, because the effect is exactly the same as not using a Viewbox at all. How- ever, you might do this from code to flip between scaled and normal-size views of a drawing. There is also a UniformToFill setting, which preserves the aspect ratio but fills the space, clipping the source in one dimension, if necessary (see Figure 3-25). The Viewbox can scale any child element—it’s not just for Canvas. However, you would rarely use it to size anything other than a draw- ing. If you were to use a Viewbox to resize some nongraphical part of your UI, it would resize any text in there as well, making it look incon- sistent with the rest of your UI. For a resizable user interface, you are best off relying on the resizable panels shown in this chapter. Figure 3-24. Viewbox with Stretch Example 3-21. Specifying a Stretch ... ... Common Layout Properties | 89 Common Layout Properties All user interface elements have a standard set of layout properties, mostly inherited from the FrameworkElement base class. These properties are shown in Table 3-2. We saw a few of these in passing in the preceding section, but we will now look at them all in a little more detail. Figure 3-25. UniformToFill Table 3-2. Common layout properties Property Usage Width Specifies a fixed width Height Specifies a fixed height MinWidth The minimum permissible width MaxWidth The maximum permissible width MinHeight The minimum permissible height MaxHeight The maximum permissible height HorizontalAlignment Horizontal position if element is smaller than available space VerticalAlignment Vertical position if element is smaller than available space Margin Space around outside of element Padding Space between element border and content Visibility Allows the element to be made invisible to the layout system where necessary FlowDirection Text direction Panel.ZIndex Controls which elements are on top or underneath RenderTransform Applies a transform without modifying the layout LayoutTransform Applies a transform that affects layout 90 | Chapter 3: Layout A couple of these properties are not from FrameworkElement. Padding is defined in sev- eral places: Control, Border, and TextBlock each define this property. It has the same meaning in all cases. It is not quite ubiquitous because padding is meaningful only on elements that have content. Panel.ZIndex may be applied to any element, but it’s not strictly inherited from FrameworkElement—it is an attached property. Width and Height You can set these properties to specify an exact width and height for your element. You should try to avoid using these—in general it is preferable to let elements deter- mine their own size where possible. It will take less effort to change your user interface if you allow elements to “size to content.” It can also simplify localization. However, you will occasionally need to provide a specific size. If you specify a Width or Height, the layout system will always attempt to honor your choices. Of course, if you make an element wider than the screen, WPF can’t make the screen any wider, but as long as what you request is possible, it will be done. MinWidth, MaxWidth, MinHeight, and MaxHeight These properties allow you to specify upper and lower limits on the size of an ele- ment. If you need to constrain your user interface’s layout, it is usually better to use these than Width and Height where possible. By specifying upper and lower limits, you can still allow WPF some latitude to automate the layout. It is possible to mandate limits that simply cannot be fulfilled. For example, if you request a MinWidth of "10000", WPF won’t be able to honor that request unless you have some very exoticdisplay hardware. In these cases,your element will be trun- cated to fit the space available. HorizontalAlignment and VerticalAlignment These properties control how an element is placed inside a parent when more room is available than is necessary. For example, a vertical StackPanel will normally be as wide as the widest element, meaning that any narrower elements are given excess space. Alignment is for these sorts of scenarios, enabling you to determine what the child element does with the extra space. The default setting for both of these properties is Stretch—when excess space is available, the element will be enlarged to fill that space. The alternatives are Left, Center, and Right for HorizontalAlignment, and Top, Center, and Bottom for VerticalAlignment. If you choose any of these, the element will not be stretched—it will use its natural height or width, and will then be positioned to one side or in the center. Common Layout Properties | 91 Margin This property determines the amount of space that should be left around the ele- ment during layout. You can specify Margin as a single number, a pair of numbers, or a list of four num- bers. When one number is used, this indicates that the same amount of space should be left on all sides. With two numbers, the first indicates the space to the left and right and the second indicates the space above and below. When four numbers are specified, they indicate the amount of space on the left, top, right, and bottom sides, respectively. You can use the Margin property to control an element’s position. For example, although Grid does not define attached properties to control the exact positioning of an element, it will honor the Margin property relative to the element’s cell. Example 3-22 shows a simple single-cell grid that uses this technique. The rectangle it contains will be 20 device-independent pixels in from the left and 10 down from the top, as Figure 3-26 shows. Note that we’ve left the last two values of the Margin property—the right and bottom margins—at zero. That’s because we only want to use the margin to specify the position of the top left of the rectangle. The position of the bottom right is determined by the rectangle’s size in this case. Padding Whereas Margin indicates how much space should be left around the outside of an ele- ment, Padding specifies how much should be left between a control’s outside and its internal content. Example 3-22. Controlling an element’s position with Margin Figure 3-26. Margin 92 | Chapter 3: Layout Padding is not present on all WPF elements, because not all elements have internal content. It is defined by the Control base class, and the Border and TextBlock classes, as well as some of the text elements described in Chapter 14. Example 3-23 shows three buttons, one with just a margin, one with both a margin and padding, and one with just padding. It also fills the area behind the buttons with color so that the effects of the margin can be seen. Figure 3-27 shows the results. The button with a margin but no padding has appeared at its normal size, but has space around it. The middle button is larger, because the padding causes space to be added around its content. The third button is larger still because it has more padding, but it has no space around it because it has no margin. Visibility The Visibility property determines whether an element is visible. It has an impact on layout, because if you set it to Collapsed, the preferred size of the element will become zero. This is different from Hidden—this indicates that although the element is not visible, the layout system should treat it in the same way as it would if it were Visible. Example 3-23. Margin versus Padding Figure 3-27. Buttons with a margin and padding Common Layout Properties | 93 FlowDirection The FlowDirection property controls how text flows; the default is based on the sys- tem locale. For example, in English-speaking locales, it will be left to right, but many cultures use the alternative right-to-left style. Setting the FlowDirection property to RightToLeft affects the flow direction of all text, and of any WrapPanel elements con- tained within that element. This is an inheritedproperty , meaning that it applies to all its descendants—setting this on a window implicitly sets it for all elements in the window. Example 3-24 shows this property applied to a WrapPanel. Figure 3-28 shows the results. Although the WrapPanel offers the most straightforward way of illustrating FlowDirection, the property’s main purpose is to control how text is arranged—its impact on WrapPanel is of secondary importance. On the face of it, a property for controlling text flow direction may seem to be unnecessary, because Unicode defines the directionality of each codepoint. If a string contains, say, Hebrew letters, these have an intrinsic right-to-left direction, and will be rendered in that direction regard- less of the FlowDirection setting. Example 3-25 shows three Hebrew letters: Alef ( ), Bet ( ), and Gimel ( ). Example 3-24. FlowDirection Figure 3-28. FlowDirection Example 3-25. Intrinsic character direction אבג 94 | Chapter 3: Layout This will appear as shown in Figure 3-29. Notice that the first character has appeared on the right, with the second and third appearing to the left. This illustrates that WPF doesn’t need to be told the flow direction for text with intrinsic directionality. And even if we explicitly set the text block’s flow direction to LeftToRight, the direc- tionality of these characters would override this setting. However, problems emerge when using characters that do not have a strong direc- tionality. Example 3-26 makes a subtle change. This adds a colon to the end of the second line, after the Hebrew characters, and the results will appear as shown in Figure 3-30. Although the three Hebrew characters have been displayed from right to left as before, the colon has been shown to the right. This is because the colon is not a right-to-left character. (Strictly speaking, Uni- code considers its directionality to be “weak.”) But because the TextBlock doesn’t have an explicit FlowDirection, the default flow direction applies—left to right, on the authors’ machines. So the colon has appeared where it normally would with left- to-right text, which is inconsistent with the right-to-left text it appears next to here. To make the colon appear in a location consistent with the directionality of the remaining text, we need to tell WPF that we would like right-to-left text flow here. This won’t affect any text with an intrinsic directionality, but it will determine where the colon appears. Example 3-27 contains a mixture of Hebrew and Latin characters to illustrate this. Figure 3-29. Right-to-left characters Example 3-26. Mixed character directions אבג: Figure 3-30. Mixed directions Example 3-27. FlowDirection אבג: Foo Common Layout Properties | 95 The sequence of characters here is three Hebrew letters, a colon, a space, and then three Latin letters. As Figure 3-31 illustrates, the Hebrew letters have been shown from right to left as they were before. But this time, the colon has been shown to the left of these letters rather than to the right, because of the FlowDirection setting. The three Latin letters appear to the left of the other letters in accordance with the RightToLeft flow direction, but because these letters all have an intrinsicleft-to-right directionality, this block of Latin letters has been displayed from left to right. The full details of the algorithm used for bidirectional layout of Unicode text is given in Annex 9 of the Unicode specification. It is too complex to describe in full detail here, but you can find it at http://www.unicode.org/reports/tr9 (http://tinysells.com/99). Panel.ZIndex Panel defines an attached property, ZIndex, that determines which element appears on top when two of them overlap. By default, the Z order of elements is determined by the order in which they are defined. Of the elements inside a particular panel, they will typically be rendered in the order in which they appear, causing the last one to appear to be “on top.” Panel.ZIndex lets you control the rendering order indepen- dently of the document order. Elements with a higher Panel.ZIndex appear on top of those with a lower Panel.ZIndex. The default value is 0, so elements with a positive Panel.ZIndex will appear on top of those that do not specify one. Example 3-28 does not use Panel.ZIndex, so the element overlapping order is determined by the order in which the elements appear. Figure 3-31. Mixed directions with RightToLeft FlowDirection Example 3-28. Without Panel.ZIndex 96 | Chapter 3: Layout This is shown on the left of Figure 3-32. The version on the right comes from Example 3-29. Example 3-29 uses Panel.ZIndex to reverse the overlap. RenderTransform and LayoutTransform You can use both the RenderTransform and LayoutTransform properties to apply a trans- form, such as scaling or rotation, to an element and all of its children. Transforms are described in Chapter 13, but it is useful to understand their impact on layout. If you apply a transform that doubles the size of an element, the element will appear to be twice as large on-screen. You would normally want the layout system to take this into account—if a Rectangle with a Width of 100 is scaled up to twice its size, it will normally make sense for the layout system to treat it as having an effective width of 200. However, you might sometimes want the transformation to be ignored for layout purposes. For example, if you are using a transform in a short animation designed to draw attention to a particular part of the UI, you probably don’t want the entire UI’s layout to be changed as a result of that animation. You can apply a transform to an object using either LayoutTransform or RenderTransform. The former causes the transform to be taken into account by the layout system, and the latter causes it to be ignored. Example 3-30 shows three but- tons, one containing untransformed content, and the other two containing content transformed with these two properties. Figure 3-32. Panel.ZIndex Example 3-29. With Panel.ZIndex Common Layout Properties | 97 Figure 3-33 shows the results. As you can see, the button with content scaled by RenderTransform has the same size border as the unscaled one. The presence of the transform has had no effect on layout, and the content no longer fits inside the space allocated for it. However, the LayoutTransform has been taken into account by the lay- out system—the third button has been enlarged in order for the scaled content to fit. The layout system deals with LayoutTransform in a straightforward manner for simple scaling transforms. The size allocated for the content is scaled up accordingly. But what about rotations? Figure 3-34 shows a button whose content has a LayoutTransform that rotates the content by 30 degrees. This is not a scaling transform, but notice that the button has grown to accommodate the content—it is taller than a normal button. Example 3-30. RenderTransform and LayoutTransform Figure 3-33. RenderTransform and LayoutTransform Figure 3-34. LayoutTransform and rotation 98 | Chapter 3: Layout When it encounters a LayoutTransform, the layout system simply applies that trans- form to the bounding box, and makes sure that it provides enough space to hold the transformed bounding box. This can occasionally lead to surprising results. Con- sider the two buttons in Example 3-31. These are shown in Figure 3-35. The top button looks as you would expect—the button is large enough to contain the graphical content. But the bottom one is rather surprising—the button appears to be taller than necessary. This result makes sense only when you consider the bounding box—remember that the layout system decides how much space to allocate by applying the LayoutTransform to the bounding box. So let’s look at it again, this time with the bounding boxes shown. Example 3-32 is a modified version of Example 3-31, with Border elements added to show the bounding box of the lines. Example 3-31. Rotation of content Figure 3-35. Rotated content Example 3-32. Rotation showing bounding box Figure 3-36. Rotated content with bounding boxes Example 3-33. Asking the impossible Example 3-32. Rotation showing bounding box (continued) 100 | Chapter 3: Layout Clearly that last button is too big to fit—it is taller than its containing panel. Figure 3-37 shows how WPF deals with this. The StackPanel has dealt with the anomaly by truncating the element that was too large. When confronted with contradictory hardcoded sizes like these, most panels take a similar approach, and will crop content where it simply cannot fit. There is some variation in the way that panels handle overflow in situations where sizes are not hardcoded, but there is still too much content to fit. Example 3-34 puts two copies of a TextBlock and its content into a StackPanel and a Grid cell. Figure 3-38 shows what happens when the available space is too narrow to hold the TextBlock at its natural length. The StackPanel has simply truncated the TextBlock. The Grid has been slightly more intelligent. It has exploited the fact that the TextBlock had wrapping enabled, and was able to flow the text into the narrow space available.* WrapPanel and DockPanel both show the same behavior. Even this technique has its limits, of course—some- times you really will have more content than fits in the space available. In that case, it may be appropriate to use a ScrollViewer, discussed presently. Figure 3-37. Truncation when content is too large Example 3-34. Handling overflow This is some text that is too long to fit. This is some text that is too long to fit. * The reason for the difference in behavior is that StackPanel uses a very simple layout mechanism. A horizon- tal StackPanel always sizes its children to content horizontally, regardless of whether there is sufficient space. ScrollViewer | 101 The reason StackPanel doesn’t result in wrapped text is that it does not attempt to constrain its children in the stacking direction: a horizontal StackPanel lets each child choose its preferred width, whether or not it fits. In effect, it pretends there is an infinite amount of space, which is why the child TextBlock didn’t attempt to wrap. StackPanel will constrain children in the other direction, though, so a vertical StackPanel would pass on the horizontal constraint, causing the TextBlock in this example to wrap. Canvas allows its children to determine both their width and their height regardless of available space, so a Canvas would fail to wrap, just like the StackPanel in this example. ScrollViewer The ScrollViewer control allows oversized content to be displayed by putting it into a scrollable area. A ScrollViewer element has a single child. Example 3-35 uses an Ellipse element, but it could be anything. If you want to put multiple elements into a scrollable view, you would nest them inside a panel. If the content of a ScrollViewer is larger than the space available, the ScrollViewer can provide scroll bars to allow the user to scroll around the content, as Figure 3-39 shows. By default, a ScrollViewer provides a vertical scroll bar, but not a horizontal one. In Example 3-35, the HorizontalScrollBarVisibility property has been set to Auto, indicating that a horizontal scroll bar should be added if required. This Auto visibility we’ve chosen for the horizontal scroll bar is different from the default vertical behavior. The VerticalScrollBarVisibility defaults to Visible, meaning that the scroll bar is present whether it is required or not. Figure 3-38. Overflow handling Example 3-35. ScrollViewer 102 | Chapter 3: Layout There are two ways to make sure a scroll bar does not appear. You can set its visibil- ity either to Disabled (the default for horizontal scroll bars) or to Hidden. The distinc- tion is that Disabled constrains the logical size of the ScrollViewer’s contents to be the same as the available space. Hidden allows the logical size to be unconstrained, even though the user has no way of scrolling into the excess space. This can change the behavior of certain layout styles. To examine how these settings affect the behavior of a ScrollViewer, we’ll start with the code shown in Example 3-36, and then show what happens as we change the ScrollViewer properties. This example shows a Grid containing three Button elements in a row. If the Grid is given more space than it requires, it will stretch the buttons to be wider than neces- sary. If it is given insufficient space, it will crop the buttons. If it is placed inside a ScrollViewer, it will be possible for the ScrollViewer to provide enough virtual, scrollable space for it, even if the space on-screen is insufficient. Figure 3-39. ScrollViewer Example 3-36. A resizable layout ScrollViewer | 103 Figure 3-40 shows how the Grid in Example 3-36 appears in a ScrollViewer when there is more than enough space. All four options for HorizontalScrollBarVisibility are shown, and in all four cases, the buttons have been stretched to fill the space. Figure 3-41 shows the same four arrangements, but with insufficient horizontal space. The top two ScrollViewer elements have horizontal scrolling enabled, with Visible and Auto, respectively. As you would expect, the ScrollViewer has provided enough space to hold all of the content, and allows the user to scroll the hidden part into view. At the bottom left, where the horizontal scroll bar is set to Hidden, the lay- out behavior is the same—it has arranged the elements as though there were enough space to hold all of them. The only difference is that it has not shown a scroll bar. (Scrolling will still occur if the user uses keyboard navigation to move the focus into the hidden area.) At the bottom right, we can see that the behavior resulting from Disabled is different. Here, not only is a scroll bar not shown, but also horizontal scrolling is disabled completely. The Grid has therefore been forced to crop the but- tons to fit into the available space. Figure 3-40. HorizontalScrollBarVisibility settings with enough space Figure 3-41. HorizontalScrollBarVisibility settings with insufficient space HorizontalScrollbarVisibility.Visible HorizontalScrollbarVisibility.Auto HorizontalScrollbarVisibility.Hidden HorizontalScrollbarVisibility.Disabled HorizontalScrollbarVisibility.Hidden HorizontalScrollbarVisibility.Visible HorizontalScrollbarVisibility.Auto HorizontalScrollbarVisibility.Disabled 104 | Chapter 3: Layout Scrollable Region and IScrollInfo If you place a panel or any other ordinary element inside a ScrollViewer, the ScrollViewer will measure its size in the normal way: the scrollable area essentially sizes to content (unless the available area is surplus to requirements, in which case the ScrollViewer gives the child all of the available space). It keeps track of the cur- rently visible region, and moves the child content around as required. Most of the time, this is exactly the behavior you require. However, occasionally you might need to take a bit more control. For example, if you have a large scrollable area containing lots of items, it might not be very efficient to create all of the items upfront. You might be able to improve per- formance significantly by creating items on demand only as they scroll into view. Such tricks require you to get more deeply involved in the scrolling process. If you want to take control of how scrolling functions, you must write a user inter- face element that implements IScrollInfo. ScrollViewer looks for this interface on its child element. If the child implements the interface, the ScrollViewer will no longer pretend that the child has all the space it requires—instead, it will tell the child exactly how much space is available on-screen for the viewport, and will defer to the child for all scrolling operations. In this case, the ScrollViewer’s role is reduced to showing scroll bars and notifying the child when the user attempts to scroll. This is not a step to be taken lightly. IScrollInfo has 24 members, and requires you to do most of the work that ScrollViewer would otherwise have done for you.* Fortunately, for the very common scenario of scrolling through a list, we can use the built-in IScrollInfo implementation provided by VirtualizingStackPanel. The VirtualizingStackPanel implements IScrollInfo so that it can show scroll feedback for all of the data, even though it only generates UI elements to repre- sent those items currently visible, “virtualizing” the view of the data. You don’t need to take any special steps to enable virtualization—a data-bound ListBox automatically displays its items using a VirtualizingStackPanel. You would need to implement IScrollInfo only if you are not using data binding, or if you need something other than a simple linear stack of items. If you customize the appearance of an ItemsControl using the template techniques described in Chapters 8 and 9, you might end up disabling virtualization. To avoid this, you should ensure that if you change the Template or ItemsPanelTemplate property of an ItemsControl, your replacement template contains a VirtualizingStackPanel. * For a full example of how to implement IScrollInfo, see a series of three articles on this subject, written by a Microsoft developer, at http://blogs.msdn.com/bencon/archive/2006/01.aspx (http://tinysells.com/64). Custom Layout | 105 We have now looked at all of the built-in mechanisms for helping you manage your application’s layout. But what if you have unusual requirements that are not met by the built-in panels? Sometimes it is necessary to customize the layout process by writ- ing your own panel. Custom Layout Although WPF supplies a flexible set of layout elements, you might decide that none of them suits your requirements. Fortunately, the layout system is extensible, and it is fairly straightforward to implement your own custom panel. To write a panel, you need to understand how the layout system works. Layout occurs in two phases: measure and arrange. Your custom panel will first be asked how much space it would like to have—that’s the measure phase. The panel should measure each of its children to find out how much space they require, and use this information to calculate how much space the panel needs in total. Of course, you can’t always get what you want. If your panel’s measure phase decides it needs an area twice the size of the screen, it won’t get that unless its parent happens to be a ScrollViewer. Moreover, even when there is enough space on-screen, your panel’s parent could still choose not to give it to you. For example, if your custom panel is nested inside a Grid, the Grid may have been set up with a hardcoded width for the column your panel occupies, in which case that’s the width you’ll get regard- less of what you asked for during the measure phase. It is only in the “arrange” phase that we find out how much space we have. During this phase, we must decide where to put all of our children as best we can in the space available. You might be wondering why the layout system bothers with the mea- sure phase when the amount of space we get during the arrange phase may be different. The reason for having both is that most panels try to take the measured size of their children into account during the arrange phase. You can think of the measure phase as asking every ele- ment in the tree what it would like, and the arrange phase as honoring those measurements where possible, compromising only where physi- cal or configured constraints come into play. Let’s create a new panel type to see how the measure and arrange phases work in practice. We’ll call this new panel DiagonalPanel, and it will arrange elements diago- nally from the top left of the panel down to the bottom right, as Figure 3-42 shows. Each element’s top-left corner will be placed where the preceding element’s bottom- right corner went. 106 | Chapter 3: Layout You don’t really need to write a new panel type to achieve this lay- out—you could get the same effect with a Grid, setting every row and column’s size to Auto. However, you could make the same argument for StackPanel and DockPanel—neither of those does anything that you couldn’t do with the Grid. It’s just convenient to have a simple single- purpose panel, as the Grid equivalent is a little more verbose. To implement this custom layout, we must write a class that derives from Panel, and that implements the measure and arrange phases. As Example 3-37 shows, we do this by overriding the MeasureOverride and ArrangeOverride methods. Figure 3-42. Custom DiagonalPanel in action Example 3-37. Custom DiagonalPanel using System; using System.Windows.Controls; using System.Windows; namespace CustomPanel { public class DiagonalPanel : Panel { protected override Size MeasureOverride( Size availableSize ) { double totalWidth = 0; double totalHeight = 0; foreach( UIElement child in Children ) { child.Measure( new Size( double.PositiveInfinity, double.PositiveInfinity ) ); Size childSize = child.DesiredSize; totalWidth += childSize.Width; totalHeight += childSize.Height; } return new Size( totalWidth, totalHeight ); } protected override Size ArrangeOverride( Size finalSize ) { Point currentPosition = new Point( ); foreach( UIElement child in Children ) { Rect childRect = new Rect( currentPosition, child.DesiredSize ); child.Arrange( childRect ); currentPosition.Offset( childRect.Width, childRect.Height ); Custom Layout | 107 Notice that the MeasureOverride method is passed a Size parameter. If the parent is aware of size constraints that will need to be applied during the arrange phase, it passes them here during the measure phase. For example, if this panel’s parent was a Window with a specified size, the Window would pass in the size of its client area during the measure phase. However, not all panels will do this. You may find the available size is specified as being Double.PositiveInfinity in both dimensions, indicating that the parent is not informing us of any fixed constraints at this stage. An infinite avail- able size indicates that we should simply pick whatever size is appropriate for our content. You must pick a finite size—returning an infinite size from your MeasureOverride will cause an exception to be thrown. Some elements ignore the available size, because their size is always determined by their contents. For example, our panel’s simple layout is driven entirely by the natu- ral size of its children, so it ignores the available size. Our MeasureOverride simply loops through all of the children, adding their widths and heights. We pass in an infi- nite size when calling Measure on each child in order to use its preferred size. You must call Measure on all of your panel’s children. If your MeasureOverride fails to measure all of its children, the layout process may not function correctly. All elements expect to be measured before they are arranged. Their arrange logic might rely on the results of calcu- lations performed during the measure phase. When you write a custom panel, it is your responsibility to ensure that child elements are mea- sured and arranged at the appropriate times. In our ArrangeOverride, we loop through all of the child elements, setting them to their preferred size, basing the position on the bottom-righthand corner of the pre- ceding element. Because this very simple layout scheme cannot adapt, it ignores the amount of space it has been given. Any child elements that do not fit will be cropped, as happens with StackPanel. This measure and arrange sequence traverses the entire user interface tree—all ele- ments use this mechanism, not just panels. A custom panel is the most appropriate place to write custom layout logic for managing the arrangement of controls. How- ever, there is one other situation in which you might want to override the MeasureOverride and ArrangeOverride methods. If you are writing a graphical ele- ment that uses the low-level visual APIs described in Chapter 13, you may want to override these methods in order for the layout system to work with your element. } return new Size( currentPosition.X, currentPosition.Y ); } } } Example 3-37. Custom DiagonalPanel (continued) 108 | Chapter 3: Layout The code will typically be simpler than for a panel, because you will not have child elements to arrange. Your MeasureOverride will simply need to report how much space it needs, and ArrangeOverride tells you how much space you have been given. Where Are We? WPF provides a wide range of options for layout. Many panel types are available, each offering its own layout style. You can then compose these into a single application in any number of ways, supporting many different user interface styles. The top-level layout will usually be set with either a Grid or a DockPanel. The other panels are typi- cally used to manage the details. You can use the common layout properties on child elements to control how they are arranged—these properties work consistently across all panel types. And if none of the built-in layout mechanisms meets your requirements, you can write your own custom panel. 109 Chapter 4 CHAPTER 4 Input4 A user interface wouldn’t be much use if it couldn’t respond to user input. In this chap- ter, we will examine the input handling mechanisms available in WPF. There are three main kinds of user input for a Windows application: mouse, keyboard, and ink.* Any user interface element can receive input—not just controls. This is not surprising, because controls rely entirely on the services of lower-level elements like Rectangle and TextBlock in order to provide visuals. All of the input mechanisms described in the fol- lowing sections are, therefore, available on all user interface element types. Raw user input is delivered to your code through WPF’s routedevent mechanism. There is also a higher-level concept of a command—a particular action that might be accessible through several different inputs such as keyboard shortcuts, toolbar but- tons, and menu items. Routed Events The .NET Framework defines a standard mechanism for managing events. A class may expose several events, and each event may have any number of subscribers. WPF augments this standard mechanism to overcome a limitation: if a normal .NET event has no registered handlers, it is effectively ignored. Consider what this would mean for a typical WPF control. Most controls are made up of multiple visual components. For example, suppose you give a button a very plain appearance consisting of a single Rectangle, and provide a simple piece of text as the content. (Chapter 9 describes how to customize a control’s appearance.) Even with such basic visuals, there are still two elements present: the text and the rectan- gle. The button should respond to a mouse click whether the mouse is over the text or the rectangle. In the standard .NET event handling model, this would mean regis- tering a MouseLeftButtonUp event handler for both elements. * Ink is input written with a stylus, whether on a Tablet PC or a hand-held device, although the mouse can be used in a pinch. 110 | Chapter 4: Input This problem would get much worse when taking advantage of WPF’s content model. A Button is not restricted to having plain text as a caption—it can contain any object as content. The example in Figure 4-1 is not especially ambitious, but even this has six visible elements: the yellow outlined circle, the two dots for the eyes, the curve for the mouth, the text, and the button background itself. Attaching event handlers for every single element would be tedious and inefficient. Fortunately, it’s not necessary. WPF uses routedevents , which are rather more thorough than normal events. Instead of just calling handlers attached to the element that raised the event, WPF walks the tree of user interface elements, calling all handlers for the routed event attached to any node from the originating element right up to the root of the user interface tree. This behavior is the defining feature of routed events, and is at the heart of event handling in WPF. Example 4-1 shows markup for the button in Figure 4-1. If one of the Ellipse ele- ments inside the Canvas were to receive input, event routing would enable the Button, Grid, Canvas, and Ellipse to receive the event, as Figure 4-2 shows. Figure 4-1. A button with nested content Example 4-1. Handling events in a user interface tree Figure 4-2. Routed events Example 4-1. Handling events in a user interface tree (continued) Button Grid Canvas TextBlock Ellipse Event 4 Event 3 Event 2 Event 1 Input 112 | Chapter 4: Input You may be wondering whether there is a meaningful difference between a direct routed event and an ordinary CLR event—after all, a direct event isn’t really routed anywhere. The main difference is that with a direct routed event, WPF provides the underlying implementa- tion, whereas if you were to use the normal C# event syntax to declare an event, the C# compiler would provide the implementation. The C# compiler would generate a hidden private field to hold the event handler, meaning that you pay a per-object overhead for each event whether or not any handlers are attached. With WPF’s event imple- mentation, event handlers are managed in such a way that you pay an overhead only for events to which handlers are attached. In a UI with thousands of elements each offering tens of events, most of which don’t have handlers attached, this starts to add up. Also, WPF’s event implementation offers something not available with ordinary C# events: attached events, which are described later. With the exception of direct events, WPF defines most routed events in pairs—one bubbling and one tunneling. The tunneling event name always begins with Preview and is raised first. This gives parents of the target element the chance to see the event before it reaches the child (hence the Preview prefix). The tunneling preview event is followed directly by a bubbling event. In most cases, you will handle only the bub- bling event—the preview would usually be used only if you wanted to be able to block the event, or if you needed a parent to do something in advance of normal handling of the event. In Example 4-1, most of the elements have event handlers specified for the PreviewMouseDown and MouseDown events—the bubbling and tunneling events, respec- tively. Example 4-2 shows the corresponding code-behind file. Example 4-2. Handling events using System; using System.Windows; using System.Diagnostics; namespace EventRouting { public partial class Window1 : Window { public Window1( ) { InitializeComponent( ); } void PreviewMouseDownButton(object sender, RoutedEventArgs e) { Debug.WriteLine("PreviewMouseDownButton"); } void MouseDownButton(object sender, RoutedEventArgs e) { Debug.WriteLine("MouseDownButton"); } Routed Events | 113 Each handler prints out a debug message. Here is the debug output we get when clicking on the Ellipse inside the Canvas: PreviewMouseDownButton PreviewMouseDownGrid PreviewMouseDownCanvas PreviewMouseDownEllipse MouseDownEllipse MouseDownCanvas MouseDownGrid This confirms that the preview event is raised first. It also shows that it starts from the Button element and works down, as we would expect with a tunneling event. The bubbling event that follows starts from the Ellipse element and works up. (Interest- ingly, it doesn’t appear to get as far as the Button. We’ll look at why this is shortly.) This bubbling routing offered for most events means that you can register a single event handler on a control, and it will receive events for any of the elements nested inside the control. You do not need any special handling to deal with nested con- tent, or controls whose appearance has been customized with templates—events simply bubble up to the control and can all be handled there. void PreviewMouseDownGrid( object sender, RoutedEventArgs e) { Debug.WriteLine("PreviewMouseDownGrid"); } void MouseDownGrid(object sender, RoutedEventArgs e) { Debug.WriteLine("MouseDownGrid"); } void PreviewMouseDownCanvas(object sender, RoutedEventArgs e) { Debug.WriteLine("PreviewMouseDownCanvas"); } void MouseDownCanvas(object sender, RoutedEventArgs e) { Debug.WriteLine("MouseDownCanvas"); } void PreviewMouseDownEllipse(object sender, RoutedEventArgs e) { Debug.WriteLine("PreviewMouseDownEllipse"); } void MouseDownEllipse(object sender, RoutedEventArgs e) { Debug.WriteLine("MouseDownEllipse"); } } } Example 4-2. Handling events (continued) 114 | Chapter 4: Input Halting Event Routing There are some situations in which you might not want events to bubble up. For example, you may wish to convert the event into something else—the Button ele- ment effectively converts a MouseDown event followed by a MouseUp event into a single Click event. It suppresses the more primitive mouse button events so that only the Click event bubbles up out of the control. (This is why the event bubbling stopped at the but- ton in the previous example.) Any handler can prevent further processing of a routed event by setting the Handled property of the RoutedEventArgs, as shown in Example 4-3. If you set the Handled flag in a Preview handler, not only will the tunneling of the Preview event stop, but also the corresponding bubbling event that would normally follow will not be raised at all. This provides a way of stopping the normal handling of an event. Determining the Target Although it is convenient to be able to handle events from a group of elements in a single place, your handler might need to know which element caused the event to be raised. You might think that this is the purpose of the sender parameter of your handler. In fact, the sender always refers to the object to which you attached the event handler. In the case of bubbled and tunneled events, this often isn’t the ele- ment that caused the event to be raised. In Example 4-1, the MouseDownGrid handler’s sender will always be the Grid itself, regardless of which element in the grid was clicked. Fortunately, it’s easy to find out which element was the underlying cause of the event. The handler has a RoutedEventArgs parameter, which offers a Source property for this purpose. This is particularly useful if you need to handle events from several different sources in the same way. For example, suppose you create a window that contains a number of graphical elements, and you’d like each to change shape when clicked. Instead of attaching a MouseDown event handler to each individual shape, you could attach a single handler to the window. All the events would bubble up from any shape to this single handler, and you could use the Source property to work out which shape you need to change. (Shapes are discussed in Chapter 13. Example 13-5 uses exactly this trick.) Example 4-3. Halting event routing with Handled void ButtonDownCanvas(object sender, RoutedEventArgs e) { Debug.WriteLine("ButtonDownCanvas"); e.Handled = true; } Routed Events | 115 Routed Events and Normal Events Normal .NET events (or, as they are often called, CLR events) offer one advantage over routed events: many .NET languages have built-in support for handling CLR events. Because of this, WPF provides wrappers for routed events, making them look just like normal CLR events.* This provides the best of both worlds: you can use your favorite language’s event handling syntax while taking advantage of the extra functionality offered by routed events. This is possible thanks to the flexible design of the CLR event mecha- nism. Though a standard simple behavior is associated with CLR events, CLR designers had the foresight to realize that some applications would require more sophisticated behavior. Classes are therefore free to imple- ment events however they like. WPF reaps the benefits of this design by defining CLR events that are implemented internally as routed events. Examples 4-1 and 4-2 arranged for the event handlers to be connected by using attributes in the markup. But we could have used the normal C# event handling syn- tax to attach handlers in the constructor instead. For example, you could remove the MouseDown and PreviewMouseDown attributes from the Ellipse in Example 4-1, and then modify the constructor from Example 4-2, as shown here in Example 4-4. When you use these CLR event wrappers, WPF uses the routed event system on your behalf. The code in Example 4-5 is equivalent to that in Example 4-4. * If you write custom elements, you should do the same. Chapter 18 describes how to do this. Example 4-4. Attaching event handlers in code ... public Window1( ) { InitializeComponent( ); myEllipse.MouseDown += MouseDownEllipse; myEllipse.PreviewMouseDown += PreviewMouseDownEllipse; } ... Example 4-5. Attaching event handlers the long-winded way ... public Window1( ) { InitializeComponent( ); myEllipse.AddHandler(Ellipse.MouseDownEvent, new MouseButtonEventHandler(MouseDownEllipse)); myEllipse.AddHandler(Ellipse.PreviewMouseDownEvent, new MouseButtonEventHandler(PreviewMouseDownEllipse)); } ... 116 | Chapter 4: Input Example 4-5 is more verbose and offers no benefit—we show it here only so that you can see what’s going on under the covers. The style shown in Example 4-4 is preferred. The code behind is usually the best place to attach event handlers. If your user inter- face has unusual and creative visuals, there’s a good chance that the XAML file will effectively be owned by a graphic designer. A designer shouldn’t have to know what events a developer needs to handle, or what the handler functions are called. Ideally, the designer will give elements names in the XAML and the developer will attach handlers in the code behind. Attached Events It is possible to define an attachedevent . This is the routed-event equivalent of an attached property: an event defined by a different class than the one from which the event will be raised. This keeps the input system open to extension. If a new kind of input device is invented, it could define new events as attached events, enabling them to be raised from any UI element. In fact, the WPF input system already works this way. The mouse, stylus, and key- board events examined in this chapter are just wrappers for underlying attached events defined by the Mouse, Keyboard, and Stylus classes in the System.Windows.Input namespace. This means we could change the Grid element in Example 4-1 to use the attached events defined by the Mouse class, as shown in Example 4-6. This would have no effect on the behavior, because the names Example 4-1 used for these events are aliases for the attached events used in this example. Handling attached events from code looks a little different. Normal CLR events don’t support this notion of attached events, so we can’t use the ordinary C# event syntax like we did in Example 4-4. Instead, we have to call the AddHandler method, passing in the RoutedEvent object representing the attached event (see Example 4-7). Alternatively, we can use the helper functions provided by the Mouse class. Example 4-8 uses this to perform exactly the same job as the preceding two examples. Example 4-6. Attached event handling Example 4-7. Explicit attached event handling myEllipse.AddHandler(Mouse.PreviewMouseDownEvent, new MouseButtonEventHandler(PreviewMouseDownEllipse)); myEllipse.AddHandler(Mouse.MouseDownEvent, new MouseButtonEventHandler(MouseDownEllipse)); Mouse Input | 117 Example 4-8 is more compact than Example 4-7 because we were able to omit the explicit construction of the delegate, relying instead on C# delegate type inference. Example 4-7 cannot do this because AddHandler can attach a handler for any kind of event, so in its function signature the second parameter is of the base Delegate type. By convention, classes that define attached events usually provide corresponding helper methods like these to let you use this slightly neater style of code. Mouse Input Mouse input is directed to whichever element is directly under the mouse cursor. All user interface elements derive from the UIElement base class, which defines a number of mouse input events. These are listed in Table 4-1. Example 4-8. Attached event handling with helper function Mouse.AddPreviewMouseDownHandler(myEllipse, PreviewMouseDownEllipse); Mouse.AddMouseDownHandler(myEllipse, MouseDownEllipse); Table 4-1. Mouse input events Event Routing Meaning GotMouseCapture Bubble Element captured the mouse. LostMouseCapture Bubble Element lost mouse capture. MouseEnter Direct Mouse pointer moved into element. MouseLeave Direct Mouse pointer moved out of element. PreviewMouseLeftButtonDown, MouseLeftButtonDown Tunnel, Bubble Left mouse button pressed while pointer inside element. PreviewMouseLeftButtonUp, MouseLeftButtonUp Tunnel, Bubble Left mouse button released while pointer inside element. PreviewMouseRightButtonDown, MouseRightButtonDown Tunnel, Bubble Right mouse button pressed while pointer inside element. PreviewMouseRightButtonUp, MouseRightButtonUp Tunnel, Bubble Right mouse button released while pointer inside element. PreviewMouseDown, MouseDown Tunnel, Bubble Mouse button pressed while pointer inside element (raised for any mouse button). PreviewMouseUp, MouseUp Tunnel, Bubble Mouse button released while pointer inside element (raised for any mouse button). PreviewMouseMove, MouseMove Tunnel, Bubble Mouse pointer moved while pointer inside element. PreviewMouseWheel, MouseWheel Tunnel, Bubble Mouse wheel moved while pointer inside element. QueryCursor Bubble Mouse cursor shape to be determined while pointer inside element. 118 | Chapter 4: Input In addition to the mouse-related events, UIElement also defines a pair of properties that indicate whether the mouse pointer is currently over the element: IsMouseOver and IsMouseDirectlyOver. The distinction between these two properties is that the former will be true if the cursor is over the element in question or over any of its child elements, but the latter will be true only if the cursor is over the element in question but not one of its children. Note that the basicset of mouse events shown in Table 4-1 does not includea Click event. This is because clicks are a higher-level concept than basic mouse input—a button can be “clicked” with the mouse, the stylus, the keyboard, or through the Windows accessibility API. Moreover, clicking doesn’t necessarily correspond directly to a single mouse event—usually, the user has to press and release the mouse button while the mouse is over the control to register as a click. Accordingly, these higher- level events are provided by more specialized element types. The Control class adds a PreviewMouseDoubleClick and MouseDoubleClick event pair. Likewise, ButtonBase—the base class of Button, CheckBox, and RadioButton—goes on to add a Click event. Mouse Input and Hit Testing WPF always takes the shapes of your elements into account when handling mouse input. Many graphical systems just use the rectangular bounding box of elements to perform hit testing (i.e., testing to see which element the mouse input “hit”). WPF does not employ this shortcut, no matter what shapes your elements may be. For example, if you create a donut-shaped control and click on the hole in the middle, the click will be delivered to whatever was visible behind your control through the hole. Occasionally it is useful to subvert the standard hit testing behavior. You might wish to create a donut-shaped control with a visible hole, but which doesn’t let clicks pass through it. Alternatively, you might want to create an element that is visible to the user, but transparent to the mouse. WPF lets you do both of these things. To achieve the first trick—transparent to the eye but opaque to the mouse—you can paint an object with a transparent brush. For example, an Ellipse with its Fill set to Transparent will be invisible to the eye, but not to the mouse. Alternatively, you can use a nontransparent brush, but make the whole element transparent by setting its Opacity property to 0. If a donut-shaped control paints such an ellipse over the hole, this enables it to receive any clicks on the hole. As far as the mouse is concerned, an element is a valid mouse target as long as it is painted with some kind of brush. The mouse doesn’t even look at the level of transparency on the brush, so it treats a com- pletely transparent brush in exactly the same way as a completely opaque brush. If you want a shape with a transparent fill that does not receive mouse input, simply supply no Fill at all. For example, you might want the shape to have an outline but no fill. If the Fill is null, as opposed to being a completely transparent brush, the shape will not act as an input target. Mouse Input | 119 WPF supports the second trick—creating a visible object that is transparent to the mouse—with the IsHitTestVisible property, which can be applied to any element. Setting this to false ensures that the element will not receive mouse input; instead, input will be delivered to whatever is under the element. For example, suppose you had written code to make some sort of graphical embellishment follow the mouse around, such as a semi-transparent ellipse to act as a halo for the pointer. Setting IsHitTestVisible to false would ensure that this visual effect had no impact on the interactive behavior. If you are using 3D (as described in Chapter 17), hit testing can be an expensive process. If you don’t require hit testing for your 3D content, making it invisible to hit testing can offer a useful performance boost. Mouse State As well as defining events, the Mouse class defines some static properties and meth- ods that you can use to discover information about the mouse or modify its state. The GetPosition method lets you discover the position of the mouse. As Example 4-9 shows, you must pass in a user interface element. It will return the mouse position relative to the specified element, taking into account any transformations that may be in effect. The Capture method allows an element to capture the mouse. Mouse capture means that all mouse input events are sent to the capturing element, even if the mouse is cur- rently outside of that element.* Example 4-10 captures the mouse to an ellipse when a mouse button is pressed, enabling it to track the movement of the mouse even if it moves outside of the ellipse. In fact, it will continue to receive MouseMove events even if the mouse moves outside of the window. This is useful for drag operations, as the user will expect an item being dragged to follow the mouse for as long as the mouse button is pressed. The capture is released by passing null to the Capture method. Example 4-9. Retrieving the mouse position Point positionRelativeToEllipse = Mouse.GetPosition(myEllipse); * Capturing the mouse does not constrain its movement. It merely controls where mouse events are delivered. Example 4-10. Mouse capture public Window1( ) { InitializeComponent( ); myEllipse.MouseDown += myEllipse_MouseDown; myEllipse.MouseMove += myEllipse_MouseMove; myEllipse.MouseUp += myEllipse_MouseUp; } 120 | Chapter 4: Input The Mouse class provides a Captured property that returns the element that has cur- rently captured the mouse; it returns null if the mouse is not captured. You can also discover which element in your application, if any, the mouse is currently over, by using the static Mouse.DirectlyOver property. Mouse provides five properties that reflect the current button state. Each returns a MouseButtonState enumeration value, which can be either Pressed or Released. Three of these properties—LeftButton, MiddleButton, and RightButton—are self-explanatory. The other two—XButton1 and XButton2—are perhaps less obvious. These are for the extra buttons provided on some mice, typically found on the side. The locations of these so-called extended buttons are not wholly consistent—one of the authors’ mice has these two buttons on the lefthand side, and another has one on each side. This explains the somewhat abstract property names. Mouse also provides an OverrideCursor property that lets you set a mouse cursor to be shown throughout your whole application, as shown in Example 4-11. This over- rides any element-specific mouse cursor settings. You could use this to temporarily show an hourglass cursor when performing some slow work. Keyboard Input The target for mouse input is always the element currently under the mouse, or the element that has currently captured the mouse. This doesn’t work so well for key- board input—the user cannot move the keyboard, and it would be inconvenient to need to keep the mouse directly over a text field while typing. Windows therefore void myEllipse_MouseDown(object sender, MouseButtonEventArgs e) { Mouse.Capture(myEllipse); } void myEllipse_MouseUp(object sender, MouseButtonEventArgs e) { Mouse.Capture(null); } void myEllipse_MouseMove(object sender, MouseEventArgs e) { Debug.WriteLine(Mouse.GetPosition(myEllipse)); } Example 4-11. Temporary mouse cursor override private void StartSlowWork( ) { Mouse.OverrideCursor = Cursors.AppStarting; ... } private void SlowWorkCompleted( ) { Mouse.OverrideCursor = null; } Example 4-10. Mouse capture (continued) Keyboard Input | 121 uses a different mechanism for directing keyboard input. At any given moment, a particular element is designated as having the focus, meaning that it acts as the target for keyboard input. The user sets the focus by clicking the control in question with the mouse or stylus, or by using navigation keys such as the Tab and arrow keys. The UIElement base class defines an IsFocused property, so in princi- ple, any user interface element can receive the focus. However, the Focusable property determines whether this feature is enabled on any particular element. By default, this is true for controls, and false for other elements. Table 4-2 shows the keyboard input events offered by user interface elements. Most of these items use tunnel and bubble routing for the preview and main events, respectively. Strictly speaking, the TextInput event is not caused exclusively by keyboard input. It represents textual input in a device-independent way, so this event can also be raised as a result of ink input from a stylus. As Table 4-2 shows, WPF makes a distinction between logical focus and keyboard focus. Only one element can have the keyboard focus at any given instant. Often, the focus will not even be in your application—the user may switch to another applica- tion. However, applications typically remember where the focus was so that if the user switches back, the focus returns to the same place as before. WPF defines the logical focus concept to keep track of this: when an application loses the keyboard focus, the last element that had the keyboard focus retains the logical focus. When the application regains the keyboard focus, WPF ensures that the focus is put back into the element with the logical focus. Table 4-2. Keyboard input events Event Routing Meaning PreviewGotKeyboardFocus, GotKeyboardFocus Tunnel, Bubble Element received the keyboard focus. PreviewLostKeyboardFocus, LostKeyboardFocus Tunnel, Bubble Element lost the keyboard focus. GotFocus Bubble Element received the logical focus. LostFocus Bubble Element lost the logical focus. PreviewKeyDown, KeyDown Tunnel, Bubble Key pressed. PreviewKeyUp, KeyUp Tunnel, Bubble Key released. PreviewTextInput, TextInput Tunnel, Bubble Element received text input. 122 | Chapter 4: Input Keyboard State The Keyboard class provides a static property called Modifiers. You can read this at any time to find out which modifier keys, such as the Alt, Shift, and Ctrl keys, are pressed. Example 4-12 shows how you might use this in code that needs to decide whether to copy or move an item according to whether the Ctrl key is pressed. Keyboard also provides the IsKeyDown and IsKeyUp methods, which let you query the state of any individual key, as shown in Example 4-13. You can also discover which element has the keyboard focus, using the static FocusedElement property, or set the focus into a particular element by calling the Focus method. The state information returned by Keyboard does not represent the cur- rent state. It represents a snapshot of the state for the event currently being processed. This means that if for some reason, your application gets bogged down and gets slightly behind in processing messages, the keyboard state will remain consistent. As an example of why this is important, consider a drag operation where the Ctrl key determines whether the operation is a move or a copy. To behave correctly, your mouse up handler needs to know the state the Ctrl key had when the mouse button was released, rather than the state that it’s in now. If the user releases the Ctrl key after letting go of the mouse button, but before your application has processed the mouse up event, the user will expect a copy operation to be per- formed, and he will be unhappy if the application performs a move simply because your code couldn’t keep up. By returning a snapshot of the keyboard state rather than its immediate state, the Keyboard class saves you from this problem. Ink Input The stylus used on Tablet PCs and other ink-enabled systems has its own set of events. Table 4-3 shows the ink input events offered by user interface elements. Example 4-12. Reading keyboard modifiers if (Keyboard.Modifiers & ModifierKeys.Control) != 0) { isCopy = true; } Example 4-13. Reading individual key state bool homeKeyPressed = Keyboard.IsKeyDown(Key.Home); Ink Input | 123 The Stylus class provides a static Capture method that works exactly the same as the Mouse.Capture method described earlier. It also offers Captured and DirectlyOver properties that do the same for the stylus as the matching properties of the Mouse class do for the mouse. There is an alternative way of dealing with stylus input. Instead of handling all of these low-level events yourself, you can use WPF’s high-level ink handling element, InkCanvas. Example 4-14 shows how little is required to add an ink input area to a WPF application. Table 4-3. Stylus and ink events Event Routing Meaning GotStylusCapture Bubble Element captured stylus. LostStylusCapture Bubble Element lost stylus capture. PreviewStylusButtonDown, StylusButtonDown Tunnel, Bubble Stylus button pressed while over element. PreviewStylusButtonUp, StylusButtonUp Tunnel, Bubble Stylus button released while over element. PreviewStylusDown, StylusDown Tunnel, Bubble Stylus touched screen while over element. PreviewStylusUp, StylusUp Tunnel, Bubble Stylus left screen while over element. StylusEnter Direct Stylus moved into element. StylusLeave Direct Stylus left element. PreviewStylusInRange, StylusInRange Tunnel, Bubble Stylus moved close enough to screen to be detected. PreviewStylusOutOfRange, StylusOutOfRange Tunnel, Bubble Stylus moved out of detection range. PreviewStylusMove, StylusMove Tunnel, Bubble Stylus moved while over element. PreviewStylusInAirMove, StylusInAirMove Tunnel, Bubble Stylus moved while over element but not in contact with screen. PreviewStylusSystemGesture, StylusSystemGesture Tunnel, Bubble Stylus performed a gesture. PreviewTextInput, TextInput Tunnel, Bubble Element received text input. Example 4-14. InkCanvas 124 | Chapter 4: Input The InkCanvas accepts free-form ink input. Figure 4-3 shows the InkCanvas in action. (It also demonstrates that I should probably stick to using the keyboard.) InkCanvas makes all of the ink input available to your program through its Strokes property. It is possible to connect this data to the handwriting recognition APIs in Windows, but that is beyond the scope of this book. Commands The input events we’ve examined give us a detailed view of user input directed at individual elements. However, it is often helpful to focus on what the user wants our application to do, rather than how she asked us to do it. WPF supports this through the command abstraction—a command is an action the application performs at the user’s request. The way in which a command is invoked isn’t usually important. Whether the user presses Ctrl-C, selects the Edit ➝ Copy menu item, or clicks the Copy button on the toolbar, the application’s response should be the same in each case: it should copy the current selection to the clipboard. The event system we examined earlier in this chapter regards these three types of input as being unrelated, but WPF’s command system lets you treat them as different expressions of the same command. The command system lets a UI element provide a single handler for a command, reducing clutter and improving the clarity of your code. It enables a more declarative style for UI elements; by associating a MenuItem or Button with a particular com- mand, you are making a clearer statement of the intended behavior than you would by wiring up Click event handlers. Example 4-15 illustrates how commands can sim- plify things. Figure 4-3. InkCanvas Example 4-15. Commands with a menu and text box Commands | 125 Each menu item is associated with a command. This is all that’s required to invoke these clipboard operations on the text box; we don’t need any code or event handlers because the TextBox class has built-in handling for these commands. More subtly, key- board shortcuts also work in this example: the built-in cut, copy, and paste commands are automatically associated with their standard keyboard shortcuts, so these work wherever you use a text box. WPF’s command system ensures that when commands are invoked, they are delivered to the appropriate target, which in this case is the text box. You are not obliged to use commands. You may already have classes to represent this idea in your own frameworks, and if WPF’s com- mand abstraction does not suit your needs, you can just handle the routed events offered by menu items, buttons, and toolbars instead. But for most applications, commands simplify the way your applica- tion deals with user input. There are five concepts at the heart of the command system: Command object An object identifying a particular command, such as copy or paste Input binding An association between a particular input (e.g., Ctrl-C) and a command (e.g., Copy) Command source The object that invoked the command, such as a Button, or an input binding Command target The UI element that will be asked to execute the command—typically the con- trol that had the keyboard focus when the command was invoked Command binding A declaration that a particular UI element knows how to handle a particular command Commands | 129 However, for commands not defined by the classes in Table 4-4, a little more infor- mation is required. The full syntax for a command attribute in XAML is: [[xmlNamespacePrefix:]ClassName.]EventName If only the event name is present, the event is presumed to be one of the standard ones. For example, Undo is shorthand for ApplicationCommands.Undo. Otherwise, you must also supply a class name and possibly a namespace prefix. The namespace pre- fix is required if you are using either custom commands, or commands defined by some third-party component. This is used in conjunction with a suitable XML namespace declaration to make external types available in a XAML file. (See Appendix A for more information on clr-namespace XML namespaces.) Example 4-19 shows the use of the command-name syntax with all the parts present. The value of m:MyAppCommands.AddToBasketCommand means that the command in ques- tion is defined in the MyNamespace.MyAppCommands class in the MyLib component, and is stored in a field called AddToBasketCommand. Because commands represent the actions performed at the user’s request, it’s likely that some commands will be invoked very frequently. It is helpful to provide key- board shortcuts for these commands in order to streamline your application for expert users. For this, we turn to input bindings. Input Bindings An input binding associates a particular form of input gesture, such as a keyboard shortcut, with a command. Two input gesture types are currently supported: a MouseGesture is a particular mouse input such as a Shift-left-click, or a right-double- click; a KeyGesture, as used in Example 4-16, is a particular keyboard shortcut. Many of the built-in commands are associated with standard gestures. For example, ApplicationCommands.Copy is associated with the standard keyboard shortcut for copying (Ctrl-C in most locales). Although a command can be associated with a set of gestures when it is created, as Example 4-17 showed, you may wish to assign additional shortcuts for the com- mand in the context of a particular window or element. To allow this, user interface elements have an InputBindings property. This collection contains InputBinding objects that associate input gestures with commands. These augment the default ges- tures associated with the command. Example 4-16 illustrated this technique—it bound the Alt-Enter shortcut to the built-in Properties command. Example 4-19. Using a custom command in XAML ... ... 130 | Chapter 4: Input Occasionally, it can be useful to disable the default input bindings. A common reason for doing this is that a particular application may have a history of using certain nonstandard keyboard shortcuts, and you wish to continue this to avoid disorienting users. For example, email software has traditionally used Ctrl-F to mean “Forward,” even though this is more commonly associated with “Find” in other applications. In most cases, you can just add a new input binding to your window, and that will override the existing binding. But what if you simply want to disassociate a particular shortcut from any command? You can do this by binding it to the special ApplicationCommands.NotACommand object. Estab- lishing an input binding to this pseudocommand effectively disables the binding. Command Source The command source is the object that was used to invoke the command. It might be a user interface element, such as a button, hyperlink, or menu item. But it can also be an input gesture. Command sources all implement the ICommandSource interface, as shown in Example 4-20. If you set the Command property to a command object, the source will invoke this com- mand when clicked, or in the case of an input gesture, when the user performs the relevant gesture. The CommandParameter property allows us to pass information to a command when it is invoked. For example, we could tell our hypothetical AddToBasket command what we would like to add to the basket, as shown in Example 4-21. The command handler can retrieve the parameter from the Parameter property of the ExecutedRoutedEventArgs, as Example 4-22 shows. (This example is a command handler for our hypothetical AddToBasketCommand. The handler would be attached with a command binding as was shown in Example 4-16.) Example 4-20. ICommandSource public interface ICommandSource { ICommand Command { get; } object CommandParameter { get; } IInputElement CommandTarget { get; } } Example 4-21. Passing a command parameter Commands | 131 Command parameters are slightly less useful if you plan to associate commands with keyboard shortcuts. Input bindings are command sources, so they also offer a CommandParameter property, but Example 4-23 shows the problem with this. This adds an input binding, associating the Ctrl-Shift-B shortcut with our AddToBasketCommand. The CommandParameter property of the binding will be passed to the command handler just as it is when the input source is a button or menu item. But of course, it will pass the same parameter every time, which limits the utility—you might just as well hardcode the value into the command handler. So in practice, you would normally use command parameters only for commands without a keyboard shortcut. If you were building a real application with shopping-basket functionality, it would probably make more sense to use data binding rather than command parameters. If you arrange for the control that invokes the command to have its data context set to the data you require, the command handler can retrieve the DataContext of the com- mand target, as Example 4-24 shows. This technique has the benefit of working even when a keyboard shortcut is used. Chapter 6 explains data contexts. The ICommandSource interface also offers a CommandTarget property. Although the inter- face defines this as a read-only property, all of the classes that implement this interface in WPF add a setter, enabling you to set the target explicitly. If you don’t set this, Example 4-22. Retrieving a command parameter void AddToBasketHandler(object sender, ExecutedRoutedEventArgs e) { string productId = (string) e.Parameter; ... } Example 4-23. Associating a command parameter with a shortcut public Window1( ) { InitializeComponent( ); KeyBinding kb = new KeyBinding(MyAppCommands.AddToBasketCommand, Key.B, ModifierKeys.Shift|ModifierKeys.Control); kb.CommandParameter = "productId4299"; this.InputBindings.Add(kb); } Example 4-24. Commands and data void AddToBasketHandler(object sender, ExecutedRoutedEventArgs e) { FrameworkElement source = (FrameworkElement) e.Source; ProductInfo product = (ProductInfo) source.DataContext; ... } 132 | Chapter 4: Input the command target will typically be the element with the input focus (although, as we’ll see later, there are some subtle exceptions). CommandTarget lets you ensure that a particular command source directs the command to a specific target, regardless of where the input focus may be. As an example of where you might use this, consider an application that uses a RichTextBox as part of a data template (introduced in Chapter 1)—you might use this to allow the user to add annotations to data items in a list. If you provided a set of buttons right next to the RichTextBox to invoke com- mands such as ToggleBold or ToggleItalic, you would want these to be applicable only to the RichTextBox they are next to. It would be confusing to the user if she clicked on one of these while the focus happened to be elsewhere in her application. By specifying a command target, you ensure that the command only ever goes where it is meant to go. Command Bindings For a command to be of any use, something must respond when it is invoked. Some controls automatically handle certain commands—the TextBox and RichTextBox han- dle the copy and paste commands for us, for example. But what if we want to pro- vide our own logic to handle a particular command? Command handling is slightly more involved than simply attaching a CLR event handler to a UI element. The classes in Table 4-4 define 144 commands, so if FrameworkElement defined CLR events for each distinct command, that would require 288 events once you include previews. Besides being unwieldy, this wouldn’t even be a complete solution—many applications define their own custom commands as well as using standard ones. The obvious alternative would be for the command object itself to raise events. How- ever, each command is a singleton—there is only one ApplicationCommands.Copy object, for example. If you were able to add a handler to a command object directly, that handler would run anytime the command was invoked anywhere in your appli- cation. What if you want to handle the command only if it is executed in a particular window or within a particular element? The CommandBinding class solves these problems. A CommandBinding object associates a specific command object with a handler function in the scope of a particular user interface element. This CommandBinding class offers PreviewExecuted and Executed events, which are raised as the command tunnels and bubbles through the UI. Command bindings are held in the CommandBindings collection property defined by UIElement. Example 4-25 shows how to handle the ApplicationCommands.New com- mand in the code behind for a window. Commands | 133 Enabling and disabling commands As well as supporting execution of commands, CommandBinding objects can be used to determine whether a particular command is currently enabled. The binding raises a PreviewCanExecute and CanExecute pair of events, which tunnel and bubble in the same way as the PreviewExecuted and Executed events. Example 4-26 shows how to handle this event for the system-defined Redo command. Example 4-25. Handling a command public partial class Window1 : Window { public Window1( ) { InitializeComponent( ); CommandBinding cmdBindingNew = new CommandBinding(ApplicationCommands.New); cmdBindingNew.Executed += NewCommandHandler; CommandBindings.Add(cmdBindingNew); } void NewCommandHandler(object sender, ExecutedRoutedEventArgs e) { if (unsavedChanges) { MessageBoxResult result = MessageBox.Show(this, "Save changes to existing document?", "New", MessageBoxButton.YesNoCancel); if (result == MessageBoxResult.Cancel) { return; } if (result == MessageBoxResult.Yes) { SaveChanges( ); } } // Reset text box contents inputBox.Clear( ); } ... } Example 4-26. Handling QueryEnabled public Window1( ) { InitializeComponent( ); CommandBinding redoCommandBinding = new CommandBinding(ApplicationCommands.Redo); redoCommandBinding.CanExecute += RedoCommandCanExecute; CommandBindings.Add(redoCommandBinding); } void RedoCommandCanExecute(object sender, CanExecuteRoutedEventArgs e) { e.CanExecute = myCustomUndoManager.CanRedo; } 134 | Chapter 4: Input Command bindings rely on the bubbling nature of command routing—the top-level Window element is unlikely to be the target of the command, as the focus will usually belong to some child element inside the window. However, the command will bub- ble up to the top. This routing makes it easy to put the handling for commands in just one place. For the most part, command routing is pretty straightforward—it usually targets the element with the keyboard focus, and uses tunneling and bub- bling much like normal events. However, there are certain scenarios where the behavior is a little more complex, so we will finish off with a more detailed look at how command routing works under the covers. Command routing All of the built-in command objects use a class called RoutedUICommand, and you will normally use this if you define application-specific commands.* RoutedUICommand pro- vides the mechanism for finding the right command binding when the command is invoked. This often needs to be determined by context. Consider Example 4-27. If the focus is in the text box when the Copy command is invoked, the text box han- dles the command itself as you would expect, copying the currently selected text to the clipboard. But not all controls have an obvious default Copy behavior. If the com- mand were invoked while the focus was in the listbox, you would need to supply * It is technically possible to provide a different class if you have special requirements. Command sources are happy to use any implementation of the ICommand interface, so you are not obliged to use the normal com- mand routing mechanism. But most applications will use RoutedUICommand. Example 4-27. Multiple command targets Commands | 135 application-specific code in order for the command to do anything. RoutedUICommand supports this by providing a mechanism for identifying the command’s target and locating the correct handler. The target of the RoutedUICommand is determined by the way in which the command was invoked. Typically, the target will be whichever element currently has the focus, unless the command source’s CommandTarget has been set. Figure 4-4 shows the con- trols and menu from Example 4-27. As you can see from the selection highlight, the TextBox at the top had the focus when the menu was opened, so you would expect it to be the target of the commands. This is indeed what happens, but it’s not quite as straightforward as you might expect. RoutedUICommand tries to locate a handler using a tunneling and bubbling system simi- lar to the one used by the event system. However, command routing has an addi- tional feature not present in normal event routing: if bubbling fails to find a handler, RoutedUICommand may try to retarget the command. This is designed for the scenario where commands are invoked by user interface elements such as menu or toolbar items because these present an interesting challenge. Example 4-27 is an example of this very scenario. It has a subtle potential problem. While the menu is open, it steals the input focus away from the TextBox. It’s unlikely that the menu item itself is the intended target for a command—it’s merely the means of invoking the command. Users will expect the Copy menu item to copy whatever was selected in the TextBox, rather than copying the contents of the menu item. The menu deals with this by relinquishing the focus when the command is exe- cuted. This causes the focus to return to the TextBox, and so the command target is the one we expect. However, there’s a problem regarding disabled commands. A command target can choose whether the commands it supports are enabled. A TextBox enables copying only if there is some selected text. It enables pasting only if the item on the clipboard is text, or can be converted to text. Menus gray out dis- abled commands, as Figure 4-4 shows. To do this, a menu item must locate the com- mand target. The problem is that the menu is in possession of the keyboard focus at the point at which it needs to discover whether the command is enabled; the appro- priate command target is therefore not the focused item in this case. Figure 4-4. Command targets and focus 136 | Chapter 4: Input The RoutedUICommand class relies on focus scopes to handle this situation. If a RoutedUICommand fails to find a command binding, it checks to see whether the initial target was in a nested focus scope. If it was, WPF finds the parent focus scope, which will typically be the window. It then retargets the command, choosing the element in the parent scope that has the logical focus (i.e., the last element to have the focus before the menu grabbed it). This causes a second tunneling and bubbling phase to occur. The upshot is that the command’s target is whichever element had the focus before the menu was opened, or the toolbar button clicked. If you are using menus or toolbars, you don’t need to do anything to make this work, because Menu and ToolBar elements both introduce nested focus scopes automati- cally. However, if you want to invoke commands from other elements, such as but- tons, you’ll need to define the focus scope explicitly. Consider Example 4-28. This associates two buttons with commands supported by a TextBox. And yet, as Figure 4-5 shows, the buttons remain disabled even when the TextBox should be able to process at least one of the commands. We can fix this by introducing a focus scope around the buttons, as Example 4-29 shows. Example 4-28. Without focus scope 142 | Chapter 5: Controls Alternatively, a button’s Command property may be set, in which case the specified command will be invoked when the button is clicked. Example 5-3 shows a button that invokes the standard ApplicationCommands.Copy command. Figure 5-2 shows the three button types provided by WPF, which offer the same behavior as the standard push-button, radio button, and checkbox controls with which any Windows user will be familiar. These all derive from a common base class, ButtonBase. This in turn derives from ContentControl, meaning that they all support its content model—you are not restricted to using simple text as the label for a button. As Figure 5-3 shows, you can use whatever content you like, although you will still get the default look for the button around or alongside your chosen content. (If you wish to replace the whole appearance of the button rather than just customize its caption, you can use a control template; see Chapter 9 for more information on templates.) It’s common practice in Windows to enable applications to be used easily from the keyboard alone. One common way of doing this is to allow buttons to be invoked by pressing the Alt key and an access key (also known as a mnemonic). The control typi- cally provides a visual hint that you can do this by underlining the relevant key when Alt is pressed. Figure 5-4 shows an example: this button can be “clicked” by press- ing Alt-B. Example 5-2. Handling a Click event void ButtonClicked(object sender, RoutedEventArgs e) { MessageBox.Show("Button was clicked"); } Example 5-3. Invoking a command with a Button Figure 5-2. Button types Figure 5-3. Buttons with nested content Figure 5-4. Button with access key Buttons | 143 WPF supports this style of keyboard access with the AccessText element. You can wrap this around some text, putting an underscore in front of the letter that will act as the access key, as shown in Example 5-4. If you really want an underscore, rather than an underlined letter, just put two underscores in a row. Earlier Windows UI frameworks used a leading ampersand to desig- nate an access key character. However, ampersands are awkward to use in XML because they have a special meaning. You need to use the character entity reference & to add an ampersand to XML. Because this is rather unwieldy, WPF uses a leading underscore instead. The AccessText element raises the AccessKeyPressedEvent attached event defined by the AccessKeyManager class. This in turn is handled by the Button, which then raises a Click event. In fact, you often don’t need to add an AccessText element explicitly. If the button’s content is purely text, you can put an underscore in it and WPF will automatically wrap it in an AccessText element for you. So in fact, Example 5-5 is all that you need. An explicit AccessText element is necessary only if you are exploiting the content model in order to put more than just text in a button. This automaticgeneration of an AccessText wrapper is available on controls for which access keys are likely to be useful. Although the buttons derive from the common ButtonBase base class, RadioButton and CheckBox derive from it indirectly via the ToggleButton class. This defines an IsChecked property, indicating whether the user has checked the button. This is of type bool? and returns null if the button is in an indeterminate state. Figure 5-5 shows how CheckBox appears for each IsChecked value. Example 5-4. AccessText Example 5-5. Access key without AccessText Figure 5-5. Checkbox IsChecked values 144 | Chapter 5: Controls Radio buttons are normally used in groups in which only one button may be selected at a time. The simplest way to group radio buttons is to give them a common parent. In Example 5-6, the two radio buttons will form a group simply because they share the same parent. Sometimes you may want to create multiple distinct groups with a common parent. You can do this by setting the GroupName property, as Example 5-7 shows. This technique also works if you want to create a single group of buttons that do not share a single parent. Slider and Scroll Controls WPF provides controls that allow a value to be selected from a range. They all offer a similar appearance and usage: they show a track, indicating the range, and a dragga- ble “thumb” with which the value can be adjusted. There is the Slider control, shown in Figure 5-6, and the ScrollBar control, shown in Figure 5-7. The main dif- ference is one of convention rather than functionality—the ScrollBar control is com- monly used in conjunction with some scrolling viewable area, and the Slider control is used to adjust values. Example 5-6. Grouping radio buttons by parent To be Not to be Example 5-7. Grouping radio buttons by name Petrol Diesel Unforced Mechanical supercharger Turbocharger Figure 5-6. Horizontal and vertical sliders ProgressBar | 145 Slider and ScrollBar are very similar in use. Both controls have an Orientation property to select between vertical and horizontal modes. They both derive from a common base class, RangeBase. This provides Minimum and Maximum properties, which define the range of values the control represents, and a Value property holding the currently selected value. It also defines SmallChange and LargeChange properties, which determine by how much the Value changes when adjusted with the arrow keys, or the Page Up and Page Down keys, respectively. The LargeChange value is also used when the part of the slider track on either side of the thumb is clicked. Whereas slider controls have a fixed-size thumb, the thumb on a scroll bar can change in size. If the scroll bar is used in conjunction with a scrollable view, the relative size of the thumb and the track is proportional to the relative size of the visible area and the total scrollable area. For example, if the thumb is about one-third the length or height of the scroll bar, this indicates that one-third of the scrollable area is currently in view. You can control the size of a scroll bar’s thumb with the ViewportSize property. The larger ViewportSize is, the larger the thumb will be. (WPF sets the ratio of the thumb and track sizes to be ViewportSize/(ViewportSize + Maximum - Minimum).) If you want to provide a scrollable view of a larger user interface area, you would not normally use the scroll bar controls directly. It is usually easier to use the ScrollViewer control, as described in Chapter 3. ProgressBar The ProgressBar control indicates how much of a long-running process the applica- tion has completed. It provides the user with an indication that work is progressing, and a rough idea of how long the user will need to wait for work to complete. As Figure 5-8 shows, it is approximately rectangular, and the nearer to completion the task is, the more of the rectangle is filled in by a color bar. If an operation is likely to take much more than a second, you should consider showing a ProgressBar to let users know how long they are likely to wait. Figure 5-7. Horizontal and vertical scroll bars Figure 5-8. ProgressBar control 146 | Chapter 5: Controls ProgressBar derives from RangeBase, the same base class as the scroll bar and slider controls discussed in the preceding section. From a developer perspective, it is very similar to these other range controls, the main difference being that it does not respond to user input—sadly, users cannot drag the progress bar indicator to the right in order to make things run faster. The progress indicator’s size is based on the Value property, so it is your application’s responsibility to update this as work progresses. Text Controls WPF provides controls for editing and displaying text. The simplest text editing con- trol is TextBox. By default, it allows a single line of text to be edited, but by setting AcceptsReturn to true, it can edit multiple lines. It provides basic text editing facili- ties: selection support, system clipboard integration (cut, paste, etc.), and multilevel undo support. Example 5-8 shows two TextBox elements, one with default settings and one in multi- line mode. Figure 5-9 shows the results. (To illustrate the multiline text box, I typed “Enter” in the middle of the text before taking the screenshot.) Example 5-8 and Figure 5-9 also show PasswordBox, which is similar to TextBox, but is designed for entering passwords. As you can see, the text in the PasswordBox has been displayed as a line of identical symbols. This is common practice to prevent passwords from being visible to anyone who can see the screen. You can set the symbol with the PasswordChar property. The PasswordBox also opts out of the ability to copy its con- tents to the clipboard. Example 5-8. TextBox and PasswordBox Figure 5-9. TextBox and PasswordBox Text Controls | 147 TextBox and PasswordBox support only plain text. This makes them easy to use for enter- ing and editing simple data. TextBox provides a Text property that represents the con- trol’s contents as a String. PasswordBox has a Password property, also of type String. The simplicity of plain text is good if you require nothing more than plain text as input. However, it is sometimes useful to allow more varied input. WPF therefore offers the RichTextBox. This edits a FlowDocument, which can contain a wide variety of content. If you want full control over the content inside a RichTextBox, you will need to work with the FlowDocument class and corresponding text object model types, which are described in Chapter 14. However, for simple formatted text support, the RichTextBox has some useful built-in behavior that does not require you to delve into the text object model. RichTextBox supports all of the commands defined by the EditingCommands class. This includes support for common formatting operations such as bold, italic, and under- line. These are bound to the Ctrl-B, Ctrl-I, and Ctrl-U keyboard shortcuts. Most of the editing commands have default keyboard input gestures, so you don’t need to do anything special to enable keyboard access to formatting operations. You could enter the example shown in Figure 5-10 entirely with the keyboard. The control also rec- ognizes the RTF format for data pasted from the clipboard, meaning that you can paste formatted text from Internet Explorer and Word, or syntax-colored code from Visual Studio. Both TextBox and RichTextBox offer built-in spellchecking. All you need to do is set the SpellCheck.IsEnabled attached property to True. As Figure 5-11 shows, this causes “red squiggly” underlines, similar to those in Microsoft Word, to appear under misspelled words. The dictionary used for spellchecking honors the standard xml:lang attribute. Example 5-9 illustrates the use of this attribute to select French. From code, setting the element’s Language property has the same effect. Figure 5-10. RichTextBox Figure 5-11. TextBox with SpellCheck.IsEnabled=“True” Example 5-9. Selecting a language for spellchecking 148 | Chapter 5: Controls As Figure 5-12 shows, this causes correctly spelled French to be accepted. But incor- rect French and correct English will be underlined. Label Some controls do not have their own built-in caption; the most widely used example is the TextBox control. Label is used to provide a caption for such controls. This might appear to be redundant, because you can achieve the same visual effect with- out a full control—you could just use the low-level TextBlock element. However, Label has an important focus handling responsibility. Well-designed user interfaces should be easy to use from the keyboard. A common way of achieving this is to provide access keys. You’ve already seen how to add an access key to a button, using either an underscore in the text, or an explicit AccessText element. This is straightforward for controls with an integral caption, such as a button. The TextBox poses slightly more of a challenge than a Button when it comes to access keys. A TextBox does not have an intrinsiccaption—theonly text it displays is the text being edited. The caption is supplied by a separate element to the left of the TextBox, as shown in Figure 5-13. This is where the Label control comes in. The purpose of the Label control is to pro- vide a place to put a caption with an access key. When the access key is pressed, the Label will redirect the focus to the relevant control, which in this case is a TextBox. Just as with a Button, you can denote a Label control’s access key by preceding the letter with an underscore. How does the Label know to which control it should redirect its access key? Label has a Target property, indicating the intended target of the access key. We use a binding expression to connect the label to its target. (We discuss binding expres- sions in detail in Chapter 6.) The expressions in Example 5-10 simply set the Target properties to refer to the named elements. Figure 5-12. French spellchecking Figure 5-13. Access key underlines ToolTip | 149 You must supply a Target. In the absence of this property, the Label control does nothing useful. In particular, it does not choose the next element in the UI tree or the Z order. Pressing the access key for a label without a target will just cause Windows to play the alert sound, indicating that it was unable to process the input. ToolTip The ToolTip control allows a floating label to be displayed above some part of the user interface. It is an unusual control in that it cannot be part of the normal user interface tree—you can use it only in conjunction with another element. It becomes visible only when the mouse pointer hovers over the target element, as Figure 5-14 shows. To associate a ToolTip with its target element, you set it as the ToolTip property of its target, as shown in Example 5-11. In fact, you don’t need to specify the ToolTip object explicitly. You can just set the ToolTip property to a string, as shown in Example 5-12. If you set the property to anything other than a ToolTip, WPF creates the ToolTip control for you, and sets its Content property to the value of the target element’s ToolTip property. Examples 5-11 and 5-12 are therefore equivalent. ToolTip derives from ContentControl, so its content is not restricted to simple strings—we can put anything we like in there, as shown in Example 5-13. Example 5-10. Label controls Figure 5-14. TextBox with ToolTip Example 5-11. Using ToolTip the long way Example 5-12. Using ToolTip the short way 150 | Chapter 5: Controls Figure 5-15 shows the results. Note that the tool tip will normally close as soon as the mouse pointer moves over it. This means that although it is possible to put inter- active elements such as buttons inside a tool tip, it’s not typically a useful thing to do, because it’s not possible to click on them. However, it is possible to subvert the auto-close behavior: you can force the tool tip to open before the user hovers over the target by setting its IsOpen property to True. This causes the tool tip to open immediately, and to remain open for as long as the target element’s window has the focus. Or if you set IsOpen to True and also set StaysOpen to False, it will open imme- diately, and remain open until you click somewhere outside of the tool tip. In these cases, you could host interactive content inside a tool tip. The ToolTip is shown in its own top-level window. This is useful for tool tips on elements near the edge of your window—if the tool tip is large enough that it flows outside of the main window, it won’t be cropped. GroupBox and Expander GroupBox and Expander are very similar controls: both provide a container for arbi- trary content and a place for a header on top. Figure 5-16 shows both controls. Aside from their different appearances, the main difference between these controls is that the Expander can be expanded and collapsed; the user can click on the arrow at the top left to hide and show the content. A GroupBox always shows its content. Both controls derive from HeaderedContentControl, which in turn derives from ContentControl. So, we can place whatever content we like directly inside the con- trol, as shown in Example 5-14. Example 5-13. Exploiting the content model in a tool tip Plain text is so last century Figure 5-15. ToolTip with mixed content GroupBox and Expander | 151 The HeaderedContentControl supports a dual form of content model: not only can the body of an Expander or GroupBox be anything you like, so can the header. Example 5-15 uses a mixture of text, video, graphics, and a control. Figure 5-16. Header and Expander controls Example 5-14. Using Header and Expander Example 5-15. Header with mixed content Hello, world Ellipse: List Controls | 153 All controls derived from ItemsControl wrap items in order to present them in a suit- able way. This process is referred to as item container generation. Each control has a corresponding container type, such as ComboBoxItem, ListBoxItem, TabItem, ListViewItem, and TreeViewItem. Although the automatic container generation can be convenient, in some cases you will want a little more control. For example, the TabControl shown in Figure 5-18 isn’t particularly useful—it has wrapped our items with tabs that have no title. To fix this, we simply provide our own TabItem elements instead of letting the TabControl generate them for us. We can then set the Header property in order to control the tab page caption, as Example 5-17 shows. Figure 5-18. Content in list controls (left to right, top to bottom: ComboBox, ListBox, TabControl, and ListView) Example 5-17. Setting tab page headers _Text Hello, world Ellipse: 154 | Chapter 5: Controls This TabControl contains the same three items as before, but this time with the TabItem elements specified explicitly. In the first of these, the Header property has been set to the text “_Button”. This uses the header’s support of the content model: this is why we can use underscores to denote accelerators. (TabItem derives from HeaderedContentControl—the same base class as GroupBox and Expander.) The other two items exploit the content model’s support for nested content—the first uses a TextBlock to control the text appearance, and the second puts an Ellipse into the header instead of text. Figure 5-19 shows the results. Providing a fixed set of elements through the Items property makes sense for tab pages and radio buttons, where you are likely to know what elements are required when you design the user interface. But this may not be the case for combo boxes and lists. To enable you to decide what items will appear at runtime, all list controls offer an alternative means of populating the list: data binding. Instead of using Items, you can provide a data source object through the ItemsSource property, and use data templates to determine how the elements appear. These techniques are described in Chapters 6 and 8. Regardless of whether you use a fixed set of items or a bound data source, you can always find out when the selected item changes by handling the relevant event: SelectedItemChanged for the TreeView and SelectionChanged for the other controls. You can then use either the SelectedItem property (supported by all controls), or SelectedIndex (supported by everything except TreeView) to find out which item is currently selected. The ListView and TreeView controls have a few extra features that make them slightly different to use than the other controls in this section. So, we will now look at the differences. Figure 5-19. TabItem headers Example 5-17. Setting tab page headers (continued) List Controls | 155 List View ListView derives from ListBox, adding support for a grid-like view. To use this, you must give the View property a GridView* object describing the columns in the list. Example 5-18 shows a simple example. Figure 5-20 shows the result. By default, the ListView sets the column sizes to be as large as necessary—either as wide as the header or as wide as required by the col- umn content. You can also specify a Width property if you prefer. The Header prop- erty supports the content model, so you are not limited to text for column headers. If you are using data binding, you will probably want to set the col- umn widths manually, because virtualization makes the auto-sizing behavior slightly unpredictable. By default, a data-bound ListView will virtualize items (i.e., it only creates the UI elements for list rows when they become visible). This significantly improves performance for lists with large numbers of items, but it means the control cannot measure every single list item upfront—it can measure only the rows it has cre- ated. So if you use automaticsizing, the columnswill be made large enough to hold the rows that are initially visible. If there are larger items further down the list and not yet in view, the columns will not be large enough to accommodate these. Our ListView isn’t very interesting yet, as it doesn’t contain any items. You can add user interface elements as children, and they will be added to the Items property as before. However, this isn’t terribly useful, because this doesn’t provide a way of filling * GridView is the only view type defined in the current version of WPF. The other view types traditionally sup- ported by the Windows list view control can all be achieved with ListBox, data binding, and the ItemsPanel property, which is described in Chapter 9. Example 5-18. Defining ListView columns Figure 5-20. A ListView with column headers 156 | Chapter 5: Controls in each column in the list. Providing explicit ListViewItem containers doesn’t help either—these don’t do anything more than the basic ListBoxItem. ListView isn’t designed to be used with user interface elements in its Items property: it is really intended for data binding scenarios. We will cover data binding in detail in the next two chapters, but in order to show the ListView in action, we must see a sneak pre- view. Example 5-19 creates a populated ListView with three columns. The control has been data-bound to the collection of FontFamily objects returned by the static Fonts.SystemFontFamilies property. This effectively fills the control’s Items collection with those FontFamily objects. The GridView then specifies three columns. The first two use the DisplayMemberBinding property to indicate what should be dis- played. The binding expressions here simply extract the Source and LineSpacing prop- erties from the FontFamily object for each row. The third column uses the alternative mechanism: the CellTemplate property. This allows you to define a DataTemplate spec- ifying arbitrary markup to be instantiated for each row—in this case a TextBlock is used, with its FontFamily property bound to the FontFamily object for the row. This allows a preview sample of the font to be generated. Figure 5-21 shows the results. Setting the DisplayMemberBinding property on a particular column causes the CellTemplate property to be ignored on that column, because the two are different mechanisms for controlling the same thing. DisplayMemberBinding is provided for convenience—it offers an easy way to display just a single piece of information from the source in a TextBlock without having to provide a complete template. Example 5-19. Populating ListView rows List Controls | 157 Because the CellTemplate property lets us put arbitrary content into a column, we are not limited to displaying fixed content. As Figure 5-22 shows, we are free to create columns that contain controls such as checkboxes and text boxes. Again, this requires the ListView to be bound to a data source, a technique that will be explained in the next chapter. But as a preview, the markup for Figure 5-22 is shown in Example 5-20. Figure 5-21. Populated ListView Figure 5-22. ListView with CheckBox and TextBox columns Example 5-20. ListView control with controls for columns 158 | Chapter 5: Controls The data source in this case is an embedded XML data island, but any data source would work. The interesting feature of this example is the use of the CellTemplate in the GridViewColumn definitions. By providing templates with controls, we have made the ListView editable. And by the wonder of data binding, when the user makes changes with these controls, those changes will be written back into the data source. Binding expressions and data templates will be explained in detail in the next two chapters. Tree View The TreeView control presents a hierarchical view, instead of the simple linear sequence of items the other list controls present. This means the TreeViewItem con- tainer needs to be able to contain nested TreeViewItem elements. Example 5-21 shows how this is done. Example 5-21. TreeView control Example 5-20. ListView control with controls for columns (continued) List Controls | 159 As Figure 5-23 shows, this defines a TreeView with nested items. Each TreeViewItem corresponds to a node in the tree, with the Header property supplying the caption for each node. This is another form of content model, allowing us to use either plain text, or, as the third of the top-level items illustrates, nested content. As with the other list controls, you can discover which item is selected with the SelectedItem property and the SelectedItemChanged event. But unlike the other con- trols, there is no SelectedIndex. Such a property makes sense for controls that present a linear list of items, but it would not work so well for a tree. Because TreeView derives from ItemsControl, it supports data binding—you can point its ItemsSource at a list of objects and it will generate a TreeViewItem for each item. Of course, the point of a tree view is to display a hierarchy of items. TreeView therefore supports hierarchical data binding, an extension of basic list binding that determines how child items are discovered. Hierarchical binding is described in Chapter 7. Figure 5-23. TreeView Example 5-21. TreeView control (continued) 160 | Chapter 5: Controls Menus Many windows applications provide access to their functionality through a hierar- chy of menus. These are typically presented either as a main menu at the top of the window, or as a pop-up “context” menu. WPF provides two menu controls. Menu is for permanently visible menus (such as a main menu), and ContextMenu is for con- text menus. Menus in pre-WPF Windows applications are typically treated differ- ently from other user interface elements. In Win32, menus get a dis- tinct handle type and special event handling provisions. In Windows Forms, most visible elements derive from a Control base class, but menus do not. This means that menus tend to be somewhat inflexi- ble—some user interface toolkits choose not to use the built-in menu handling in Windows simply to avoid the shortcomings. In WPF, menus are just normal controls, so they do not have any special fea- tures or restrictions. Both kinds of menus are built in the same way—their contents consist of a hierarchy of MenuItem elements. Example 5-22 shows a typical example. Example 5-22. A main menu Menus | 161 Figure 5-24 shows the results. ContextMenu is used in a very similar way, although the appearance is different. The top level of a Menu appears as a horizontal bar, which you would typically put at the top of a window, but context menus do not have this bar, their top level consisting of a pop up. This means that a context menu needs a UI element from which to launch this pop up. You attach a context menu to an element by setting that element’s ContextMenu property. Example 5-23 shows a Grid element with a ContextMenu. With this context menu in place, a right-click anywhere on the grid will bring up the context menu. (The grid’s Background property has been set to ensure that this will work—if the Background has its default null value, the grid will effectively be invisi- ble to the mouse unless the mouse is over one of the grid’s children. Using a Transparent brush makes the grid visible to the mouse, without making it visually opaque.) Figure 5-25 shows the context menu in action. Figure 5-24. Menu Example 5-23. Grid with ContextMenu ... Figure 5-25. Context menu 162 | Chapter 5: Controls Each MenuItem has a Header property. For children of a Menu, the header determines the label shown on the menu bar. For a MenuItem nested either in a ContextMenu or inside another MenuItem, the Header contains the content for that menu line. The Header property supports the content model, so it allows either plain text with optional underscores to denote access keys, as shown in Example 5-22, or nested content. Example 5-24 shows a modified version of one of the menu items, exploit- ing the ability to add structure in order to add some graphics into the menu. Note that it’s now necessary to supply an AccessText element if we want an access key. With plain-text headers, this element was generated for us automatically, but once nested content is in use, we need to define it explicitly. Figure 5-26 shows the results. The menu in Example 5-22 doesn’t do anything useful, because there are no event handlers or commands specified. There are two ways in which you can hook a MenuItem up to some code. You can handle its Click event in much the same way that you would handle a button click. Alternatively, you can set the Command property on the MenuItem, as was described in Chapter 4. Example 5-25 shows a modified version of the Edit submenu with menu items asso- ciated with the relevant standard commands. As long as the focus is in a control such as TextBox or RichTextBox that understands these standard commands, the com- mands will be handled without needing any explicit coding. If the focus is not in such a control, the commands will simply bubble up. For example, the command can be handled by a command binding registered for the window. If nothing handles the command, it will be ignored. Example 5-24. Nesting content inside MenuItem.Header _New... Figure 5-26. Menu with nested content Example 5-25. MenuItems with commands Menus | 163 If you were to remove the Header properties from Example 5-25, you would find that the menu items all still appear with the correct header text for the commands. This is because RoutedUICommand knows the display name for the command it represents, and MenuItem is able to extract the name. However, there is one problem with taking advan- tage of this: you will lose the accelerators. RoutedUICommand cannot prescribe a particular access key, because access keys should be unique within the scope of a particular menu. If a menu assigns the same access key to more than one item in a menu, ambiguity ensues, and pressing the access key will simply highlight the menu item rather than selecting it, with further key presses alternating between the choices. This significantly reduces how effectively access keys streamline user input. To guarantee a unique key for each menu item, a developer must coordinate access keys with knowledge of which commands are used in which menus. So, the appro- priate place to assign access keys is the menu, not the command. Imagine you’re writing a custom command of your own—how would you choose which access key to use? You would be able to choose only if you knew what other commands will be sharing a menu with your command. Now consider WPF’s built-in commands— these will be used in all sorts of contexts in any number of applications, and because there are considerably more built-in commands than there are keys on the keyboard, Microsoft cannot possibly assign access keys in a way guaranteed to prevent ambigu- ity. Commands therefore don’t get to specify the access key. So, in practice, you will normally want to define the Header property for menu items associated with com- mands, even though it may appear to be optional. Menu items often have a shortcut key as well as an access key. The access key works only when the menu is open. A shortcut such as Ctrl-S (for save) works whether the menu is open or not. Of course, the menu isn’t responsible for binding the control shortcut to the key gesture—as we saw in Chapter 4, we associate inputs with com- mands using input bindings. However, menus conventionally display shortcuts in order to help users discover them. If a menu item’s Command has an associated shortcut key, WPF will automatically dis- play this in the menu. Example 5-25 uses standard clipboard and undo/redo com- mands, and these all have default shortcuts, so the menu reflects this, as you can see in Figure 5-27. Example 5-25. MenuItems with commands (continued) 164 | Chapter 5: Controls If, for some reason, you choose not to use WPF’s command system—maybe you have an existing application framework that provides its own command abstrac- tion—you can still display a shortcut. MenuItem provides an InputGestureText prop- erty that lets you choose the text that appears in the normal place for such shortcuts. Example 5-26 shows a menu item with both a shortcut and an access key. Menu and ContextMenu both derive indirectly from ItemsControl, the same base class as all of the list controls. This means that you can use the ItemsSource property to populate a menu using hierarchical data binding rather than fixed content. This could be useful if you want to make your menu structure reconfigurable. See Chapter 6 for more details on how to use data binding. Toolbars Most Windows applications offer toolbars as well as menus. Toolbars provide faster access for frequently used operations, because the user does not need to navigate through the menu system—the toolbar is always visible on-screen. Figure 5-28 shows a pair of typical toolbars. Figure 5-27. Automatic shortcut display Example 5-26. Menu item with shortcut and access key Figure 5-28. Application with toolbars Toolbars | 165 WPF supports toolbars through the ToolBarTray and ToolBar controls. ToolBarTray provides a container into which you can add multiple ToolBar elements. Example 5-27 shows a simple example with two toolbars; this is the markup for the toolbars in Figure 5-28. Example 5-27. ToolBarTray and ToolBar Choice 166 | Chapter 5: Controls This contains just two toolbars, with a couple of buttons each. In this example, we have used some simple vector graphics to draw the usual New and Open icons. The graphical elements used are explained in more detail in Chapter 13. In practice, you would rarely put graphics inline like this—you would usually expect drawings to be resources that are simply referred to by the buttons in the toolbar. See Chapter 12 for more details. The second toolbar just uses the default visuals for a Button and a CheckBox. As you can see, these take on a flat, plain appearance when they appear in a toolbar. Because toolbar buttons are just normal Button or CheckBox elements with special- ized visuals, there is nothing particularly special about their behavior. Toolbars just provide a particular way of arranging and presenting controls. You can also add other elements such as a TextBox or ComboBox. These will just be arranged on the tool- bar along with the buttons. GridSplitter GridSplitter lets you offer the user a way to adjust the layout of your application, by changing the size of a column or row in a grid. This lets you provide a similar feature to Windows Explorer, where if you turn on the folder view, or one of the other pan- els that can appear on the lefthand side of a window, you can change the amount of space available to the panel by dragging on the vertical bar between the panel and the main area. You can use GridSplitter only to rearrange a Grid panel (see Example 5-28). This puts a GridSplitter into the middle of the three columns. As Figure 5-29 shows, if the user moves the mouse over the GridSplitter, the mouse pointer changes to the horizontal resize arrow. Dragging the slider resizes the columns on either side. Example 5-28. GridSplitter Where Are We? | 167 Where Are We? Controls are the building blocks of applications. They represent the features of the interface with which the user interacts. Controls provide behavior, and they rely on styling and templates to present an appearance. WPF provides a set of built-in controls based on the controls commonly used in Windows applications. WPF significantly reduces the need for custom controls. In part, this is enabled by content models, but as we will see in Chapters 8 and 9, the extent to which built-in controls can be cus- tomized means that custom controls are necessary only in the most specialized of circumstances. Figure 5-29. GridSplitter 168 Chapter 6CHAPTER 6 Simple Data Binding 6 The purpose of most applications is to display data to users and, often, to let them edit that data. Your job as the application developer is to bring the data in from a variety of sources that expose their data in object, hierarchical, or relational format. Regardless of where the data comes from or the format it’s in, there are several things that you’ll most likely need to do with the data, including showing it, converting it, sorting it, filtering it, grouping it, relating one part of it to another part, and, more often than not, editing it. Without some kind of engine for shuttling data back and forth between data sources and controls, you’re going to be writing a great deal of code. With WPF’s data binding engine, you get more features with less code, which is always a nice place to be. Without Data Binding Consider a very simple application for editing a single person’s name and age, as shown in Figure 6-1. Figure 6-1 can be implemented with some simple XAML, as shown in Example 6-1. Figure 6-1. An exceedingly simple application Without Data Binding | 169 We can represent the data to be shown in our simple application in a simple class (see Example 6-2). With the Person class, Example 6-3 shows a naïve implementation of the UI of our application. Example 6-1. A simple Person editor layout ... Name: Age: Example 6-2. A simple Person class public class Person { string name; public string Name { get { return this.name; } set { this.name = value; } } int age; public int Age { get { return this.age; } set { this.age = value; } } public Person( ) {} public Person(string name, int age) { this.name = name; this.age = age; } } Example 6-3. Naïve Person editor code // Window1.xaml.cs ... public class Person {...} public partial class Window1 : Window { Person person = new Person("Tom", 11); 170 | Chapter 6: Simple Data Binding The code in Example 6-3 creates a Person object and initializes the text boxes with the Person object properties. When the Birthday button is pressed, the Person object’s Age property is incremented and the updated Person data is shown in a mes- sage box, as shown in Figure 6-2. Our simple application implementation is, in fact, too simple. The change in the Person Age property does show up in the message box, but it does not show up in the main window. One way to keep the application’s UI up-to-date is to write code that, whenever a Person object is updated, manually updates the UI at the same time: void birthdayButton_Click(object sender, RoutedEventArgs e) { ++person.Age; public Window1( ) { InitializeComponent( ); // Fill initial person fields this.nameTextBox.Text = person.Name; this.ageTextBox.Text = person.Age.ToString(); // Handle the birthday button click event this.birthdayButton.Click += birthdayButton_Click; } void birthdayButton_Click(object sender, RoutedEventArgs e) { ++person.Age; MessageBox.Show( string.Format( "Happy Birthday, {0}, age {1}!", person.Name, person.Age), "Birthday"); } } Figure 6-2. Our simple application is too simple Example 6-3. Naïve Person editor code (continued) Without Data Binding | 171 // Manually update the UI this.ageTextBox.Text = person.Age.ToString(); MessageBox.Show( string.Format( "Happy Birthday, {0}, age {1}!", person.Name, person.Age), "Birthday"); } With a single line of code, we’ve “fixed” our application. This is a seductive and pop- ular road, but it does not scale as the application gets more complicated and requires more of these “single” lines of code. To get beyond the simplest of applications, we’ll need something better. Object Changes A more robust way for the UI to track object changes is for the object to raise an event when a property changes. The right way for an object to do this is with an implementation of the INotifyPropertyChanged interface, as shown in Example 6-4. Example 6-4. A class that supports property change notification using System.ComponentModel; // INotifyPropertyChanged ... public class Person : INotifyPropertyChanged { // INotifyPropertyChanged Members public event PropertyChangedEventHandler PropertyChanged; protected void Notify(string propName) { if( this.PropertyChanged != null ) { PropertyChanged(this, new PropertyChangedEventArgs(propName)); } } string name; public string Name { get { return this.name; } set { if( this.name == value ) { return; } this.name = value; Notify("Name"); } } int age; public int Age { get { return this.age; } set { if(this.age == value ) { return; } this.age = value; 172 | Chapter 6: Simple Data Binding In Example 6-4, when either of the Person properties changes (due to the implementa- tion of the Birthday button Click handler), a Person object raises the PropertyChanged event. We could use this event to keep the UI synchronized with the Person properties, as shown in Example 6-5. Notify("Age"); } } public Person( ) {} public Person(string name, int age) { this.name = name; this.age = age; } } Example 6-5. Simple Person editor code // Window1.xaml.cs ... public class Person : INotifyPropertyChanged {...} public partial class Window1 : Window { Person person = new Person("Tom", 11); public Window1( ) { InitializeComponent( ); // Fill initial person fields this.nameTextBox.Text = person.Name; this.ageTextBox.Text = person.Age.ToString( ); // Watch for changes in Tom's properties person.PropertyChanged += person_PropertyChanged; // Handle the birthday button click event this.birthdayButton.Click += birthdayButton_Click; } void person_PropertyChanged( object sender, PropertyChangedEventArgs e) { switch( e.PropertyName ) { case "Name": this.nameTextBox.Text = person.Name; break; case "Age": this.ageTextBox.Text = person.Age.ToString(); break; } Example 6-4. A class that supports property change notification (continued) Without Data Binding | 173 Example 6-5 shows an example of a single instance of the Person class that’s created when the main window first comes into existence, initializing the name and age text boxes with the initial person values and then subscribing to the property change event to keep the text boxes up-to-date as the Person object changes. With this code in place, the Birthday button Click event handler doesn’t have to manually update the text boxes when it updates Tom’s age; instead, updating the Age property causes a cascade of events that keeps the age text box up-to-date with the Person object’s changes, as shown in Figure 6-3. } void birthdayButton_Click(object sender, RoutedEventArgs e) { ++person.Age; // person_PropertyChanged will update ageTextBox MessageBox.Show( string.Format( "Happy Birthday, {0}, age {1}!", person.Name, person.Age), "Birthday"); } } Figure 6-3. Keeping the UI up-to-date with changes in the object Example 6-5. Simple Person editor code (continued) birthdayButton_Click Window1 Person person_PropertyChanged Age TextBox MessageBox.Show Age .getAge .setAge PropertyChanged 1 3 2 7 6 5 4 174 | Chapter 6: Simple Data Binding The steps are as follows: 1. User clicks on button, which causes Click event to be raised. 2. Click handler gets the age (11) from the Person object. 3. Click handler sets the age (12) on the Person object. 4. Person Age property setter raises the PropertyChanged event. 5. PropertyChanged event is routed to event handler in the UI code. 6. UI code updates the age TextBox from “11” to “12.” 7. Button click event handler displays a message box showing the new age (“12”). By the time the message box is shown with Tom’s new age, the age text box in the window has already been updated, as shown in Figure 6-4. By handling the PropertyChanged event, we ensure that when the data changes, the UI is updated to reflect that change. However, that solves only half the problem; we still need to handle changes in the UI and reflect them back to the object. UI Changes Without some way to track changes from the UI back into the object, we could eas- ily end up with a case where the user has made some change (like changing the per- son’s name), shows the object’s data (as happens when clicking the Birthday button), and expects the change to have been made, only to be disappointed with Figure 6-5. Notice in Figure 6-5 that the Name is “Thomsen Frederick” in the window, but “Tom” in the message box, which shows that although the UI has been updated, the under- lying object has not. To fix this problem, we need only watch for the Text property in our TextBox object to change, updating the Person object as appropriate (see Example 6-6). Figure 6-4. Manually populating two WPF controls with two object properties Without Data Binding | 175 Figure 6-5. The need to keep UI and data in sync Example 6-6. Tracking changes in the UI public partial class Window1 : Window { Person person = new Person("Tom", 11); public Window1( ) { InitializeComponent( ); // Fill initial person fields this.nameTextBox.Text = person.Name; this.ageTextBox.Text = person.Age.ToString( ); // Watch for changes in Tom's properties person.PropertyChanged += person_PropertyChanged; // Watch for changes in the controls this.nameTextBox.TextChanged += nameTextBox_TextChanged; this.ageTextBox.TextChanged += ageTextBox_TextChanged; // Handle the birthday button click event this.birthdayButton.Click += birthdayButton_Click; } ... void nameTextBox_TextChanged(object sender, TextChangedEventArgs e) { person.Name = nameTextBox.Text; } void ageTextBox_TextChanged(object sender, TextChangedEventArgs e) { int age = 0; if( int.TryParse(ageTextBox.Text, out age) ) { person.Age = age; } } 176 | Chapter 6: Simple Data Binding Figure 6-6 shows the name changes in the UI correctly propagating to the Person object. Now, regardless of where the data changes, both the Person object and the UI show- ing the Person object are kept synchronized. And although we’ve gotten the function- ality we wanted, we had to write quite a bit of code to make it happen: • Window1 constructor code to set controls to initial values, converting data to strings as appropriate • Window1 constructor code to hook up the PropertyChanged event to track the Person object’s property changes • PropertyChanged event handler to grab the updated data from the Person object, converting data to strings as appropriate • Window1 constructor code to hook up the TextBox object’s TextChanged event to track the UI changes • TextChanged event handlers to push the updated TextBox data into the Person object, converting the data as appropriate void birthdayButton_Click(object sender, RoutedEventArgs e) { ++person.Age; // nameTextBox_TextChanged and ageTextBox_TextChanged // will make sure the Person object is up-to-date // when it's displayed in the message box MessageBox.Show( string.Format( "Happy Birthday, {0}, age {1}!", person.Name, person.Age), "Birthday"); } } Figure 6-6. Manually keeping properties and controls in sync Example 6-6. Tracking changes in the UI (continued) Data Binding | 177 This code allows us to write our Birthday button Event handler safe in the knowl- edge that all changes are synchronized when we display the message box. However, it’s easy to imagine how this code could quickly get out of hand as the number of object properties or the number of objects we’re managing grows. Plus, this seems like such a common thing to want to do that someone must have already provided a simpler way to do this. And in fact, someone has; it’s called data binding. Data Binding Our manual code to keep the data and the UI synchronized has the effect of manu- ally binding together two pairs of properties, each pair composed of one property on the Person object and the Text property on a TextBox object. In WPF, data binding is the act of registering two properties with the data binding engine and letting the engine keep them synchronized, converting types as appropriate, as shown in Figure 6-7. Bindings We can register two properties to be kept in sync by the data binding engine using an instance of a Binding object, as shown in Example 6-7. In Example 6-7, we’ve used the property element syntax introduced in Chapter 1 to create an instance of the Binding markup extension class and initialize its Path prop- erty to Age. This establishes the synchronization relationship with the Text property of the TextBox object. Using the binding markup extension syntax (also introduced in Chapter 1), we can shorten Example 6-7 to the code snippet shown in Example 6-8. Figure 6-7. The synchronization and conversion duties of data binding Example 6-7. Binding a UI target property to a data source property Example 6-8. The shortcut binding syntax Dependency Property Element Object Property Synchronization Data binding Conversion 178 | Chapter 6: Simple Data Binding As an even shorter cut, you can drop the Path designation altogether and the Binding will still know what you mean (see Example 6-9). I prefer to be more explicit, so I won’t use the syntax in Example 6-9, but I won’t judge if you like it. As an example of something more exotic, Example 6-10 sets more than one attribute of a binding. We’ll see what all of these Binding properties mean directly. You might also be inter- ested in how to pack multiple binding attribute settings using the shortcut syntax. To accomplish this, simply comma-delimit the name-value pairs, using spaces and newlines as convenient (see Example 6-11). Table 6-1 shows the list of available properties on a Binding object, many of which you’ll see described in more detail later in this chapter. Example 6-9. The shortest cut binding syntax Example 6-10. A more full-featured binding example, longhand Example 6-11. A more full-featured binding example, shorthand Table 6-1. The Binding class’s properties Property Meaning BindsDirectlyToSource Defaults to False. If set to True, indicates a binding to the parameters of a DataSourceProvider (like the ObjectDataProvider discussed later in this chapter), instead of to the data returned from the provider. See the BindingToMethod sample included with this book for an example. Converter An implementation of IValueConverter to use to convert values back and forth from the data source. Discussed later in this chapter. ConverterCulture Optional parameter passed to the IValueConverter methods indicating the culture to use during conversion. ConverterParameter Optional application-specific parameter passed to the IValueConverter meth- ods during conversion. ElementName Used when the source of the data is a UI element as well as the target. Discussed later in this chapter. Data Binding | 179 The Binding class has all kinds of interesting facilities for managing the binding between two properties, but the one that you’ll most often set is the Path property.* For most cases, you can think of the Path as the name of the property on the object serving as the data source. So, the binding statement in Example 6-8 is creating a binding between the Text property of the TextBox and the Name property of some object to be named later, as shown in Figure 6-8. FallbackValue The value to use in case retrieving the value from the data source has failed, one of the parts of a multipart path is null, or the binding is asynchronous and the value hasn’t yet been retrieved. IsAsync Defaults to False. When set to True, gets and sets the data on the source asyn- chronously. Uses the FallbackValue while the data is being retrieved. Mode One of the BindingMode values: TwoWay, OneWay, OneTime, OneWayToSource, or Default. NotifyOnSourceUpdated Defaults to False. Whether to raise the SourceUpdated event or not. NotifyOnTargetUpdated Defaults to False. Whether to raise the TargetUpdated event or not. NotifyOnValidationError Defaults to False. Whether to raise the Validation.Error attached event or not. Discussed later in this chapter. Path Path to the data of the data source object. Use the XPath property for XML data. RelativeSource Used to navigate to the data source relative to the target. Discussed later in this chapter. Source A reference to the data source to be used instead of the default data context. UpdateSourceExceptionFilter Optional delegate to handle errors raised while updating the data source. Valid only if accompanied by an ErrorValidationRule (discussed later in this chapter). UpdateSourceTrigger Determines when the data source is updated from the UI target. Must be one of the UpdateSourceTrigger values: PropertyChanged, LostFocus, Explicit, or Default. Discussed in Chapter 7. ValidationRules Zero or more derivations of the ValidationRule class. Discussed later in this chapter. XPath XPath to the data on the XML data source object. Use the Path property for non- XML data. Discussed in Chapter 7. * Or XPath property, if your data is XML, which is discussed in Chapter 7. Figure 6-8. Binding targets and sources Table 6-1. The Binding class’s properties (continued) Property Meaning BindingTextBox.Text .Name Binding Target Binding Source 180 | Chapter 6: Simple Data Binding In this binding, the TextBox control is the binding target, as it acts as a consumer of changes to the binding source, which is the object that provides the data. The bind- ing target can be any WPF element, but you’re only allowed to bind to the element’s dependency properties (described in Chapter 1). On the other hand, you can bind to any public CLR property or dependency prop- erty on the binding source object.* The binding source is not named in this example specifically so that we can have some freedom as to where it comes from at runtime and so that it’s easier to bind multiple controls to the same object (like our name and age text box controls bound to the same Person object). Commonly, the binding source data comes from a data context. Implicit Data Source A data context is a place for bindings to look for the data source if they don’t have any other special instructions (which we’ll discuss later). In WPF, every FrameworkElement and every FrameworkContentElement has a DataContext property. The DataContext property is of type Object, so you can plug anything you like into it (e.g., string, Person, List, etc.). When looking for an object to use as the binding source, the binding object logically traverses up the tree from where it’s defined, looking for a DataContext property that has been set.† This traversal is handy because it means that any two controls with a common logi- cal parent can bind to the same data source. For example, both of our text box con- trols are children of the grid, and they each search for a data context, as shown in Figure 6-9. * WPF data binding sources can also expose data via implementations of ICustomTypeDescriptor, which is how ADO.NET’s data sources are supported. † Actually, data binding doesn’t do any searching at runtime. Instead, it relies on the fact that the DataContext property is inheritable, which means that the WPF property system itself implements the scoping/searching behavior described here. (Inheritable dependency properties are described in Chapter 18.) Figure 6-9. Searching the element tree for a non-null DataContext ... Name: Age: 1 2 3 Data Binding | 181 The steps work like this: 1. The binding looks for a DataContext that has been set on the TextBox itself. 2. The binding looks for a DataContext that has been set on the Grid. 3. The binding looks for a DataContext that has been set on the Window. Providing a DataContext value for both of the text box controls is a matter of setting the shared Person object as a value of the grid’s DataContext property in the Window1 constructor, as shown in Example 6-12. So, although the functionality of our app is the same as shown in Figure 6-6, the data synchronization code has been reduced to a binding object for each property in the XAML where data is to be shown and a data context for the bindings to find the data. There is no need for the UI initialization code or the event handlers that copy and convert the data (notice that no code has been elided from Example 6-12). To be clear, the use of the INotifyPropertyChanged implementation is a requiredpart of this example. This is the interface that WPF’s data binding engine uses to keep the Example 6-12. Editor code simplified with data binding // Window1.xaml.cs using System; using System.Windows; using System.Windows.Controls; namespace WithBinding { public partial class Window1 : Window { Person person = new Person("Tom", 11); public Window1( ) { InitializeComponent( ); // Let the grid know its data context grid.DataContext = person; this.birthdayButton.Click += birthdayButton_Click; } void birthdayButton_Click(object sender, RoutedEventArgs e) { // Data binding keeps person and the text boxes synchronized ++person.Age; MessageBox.Show( string.Format( "Happy Birthday, {0}, age {1}!", person.Name, person.Age), "Birthday"); } } } 182 | Chapter 6: Simple Data Binding UI synchronized when an object’s properties change. Without it, a UI change can still propagate to the object, but the binding engine will have no way of knowing when the object changes outside of the UI. It’s not quite true that the binding engine will have no way of knowing when a change happens on an object that does not implement the INotifyPropertyChanged interface. Another way it can know is if the object implements the PropertyNameChanged events as proscribed in .NET 1.x data binding (e.g., SizeChanged, TextChanged, etc.), with which WPF maintains backward compatibility. Another way is a manual call to the UpdateTarget method on the BindingExpression object associated with the Binding in question. For example: BindingOperations.GetBindingExpression( ageTextBox, TextBox.TextProperty).UpdateTarget( ); Without rebinding or setting the data again manually, the call to UpdateTarget is your only option if the data source provides no notifi- cations and you have no access to the source code. However, it’s safe to say that an implementation of INotifyPropertyChanged is the recom- mended way to enable property change notifications in WPF data binding. Data Islands Although our application is attempting to simulate a more complicated application that, perhaps, loads its “person data” from some serialized form and saves it between application sessions, it’s not hard to imagine cases where some data is known at compile time (e.g., sample data like our Tom). As discussed in Chapter 1, XAML is a language for describing object graphs, so prac- tically any type with a default constructor can be initialized in XAML (the default constructor is needed because XAML has no syntax for calling a nondefault con- structor).* Luckily, as you’ll recall from Example 6-4, our Person class has a default constructor, so we can create an instance of it in our application’s XAML, as shown in Example 6-13. * If you want to get fancy, you can create a TypeConverter that can accept a string as input or a markup exten- sion as well, but generally the default constructor route is the easiest way to provide XAML support for your custom types. Example 6-13. Creating an instance of a custom type in XAML ... ... Name: Age: Example 6-15. Using an object bound in XAML public partial class Window1 : Window { ... void birthdayButton_Click(object sender, RoutedEventArgs e) { // Get the Person from the Window's resources Person person = (Person)this.FindResource("Tom"); ++person.Age; MessageBox.Show(...); } } 184 | Chapter 6: Simple Data Binding Explicit Data Source Once you’ve got yourself a named source of data, you can be explicit in the XAML about the source in the binding instead of relying on implicitly binding to a DataContext property set somewhere in the tree. Being explicit is useful if you’ve got more than one source of data (e.g., two Person objects). Setting the source explicitly is accomplished with the Source property in the binding, as shown in Example 6-16. In Example 6-16, we’ve bound two text boxes to two different Person objects, set- ting the Source property of the Binding object to each person explicitly. Binding to Other Controls As another example of using explicit data sources, WPF provides for binding one ele- ment’s property to another element’s property. For instance, if we wanted to syn- chronize the brush used to draw the Birthday button’s text with the foreground brush of the age text box (this will be handy later when we change the age text box’s color based on the person’s age), we can use the ElementName property of the Binding object, as shown in Example 6-17. Example 6-16. Data binding using the Source property ... ... Example 6-17. Binding to another UI element Data Binding | 185 Now, no matter what means we use to change the foreground brush’s color of the age text box—via binding, code, or triggers (as we’ll see in Chapter 8)—the button’s foreground brush will always follow. Value Conversion In Example 6-17, we’ve bound the foreground brush of the Birthday button to what- ever the foreground brush is for the age text box, but our text box never changes color, so neither will the Birthday button. However, we might decide that anyone over age 25 is hot, so should be marked in the UI as red.* When someone ages at the click of the Birthday button, we want to keep the UI up-to-date, which means we’ve got ourselves a perfect candidate for data binding—something along the lines of Example 6-18. In Example 6-18, we’ve bound the age text box’s Text property to the Person object’s Age property, as we’ve already seen, but we’re also binding the Foreground property of the text box to the same property on the Person object. As Tom’s age changes, we want to update the foreground color of the age text box. However, because the Age is of type Int32 and Foreground is of type Brush, a mapping from Int32 to Brush needs to be applied to the data binding from Age to Foreground. That’s the job of a value converter. A value converter (or just “converter” for short) is an implementation of the IValueConverter interface, which contains two methods: Convert and ConvertBack. * Or, anyone over 25 is in more danger of dying and red means “danger”—whichever makes you more likely to recommend this book to your friends... Example 6-18. Binding to a non-Text property ... ... Example 6-17. Binding to another UI element (continued) 186 | Chapter 6: Simple Data Binding The Convert method is called when converting from the source data to the target UI data (e.g., from Int32 to Brush). The ConvertBack method is called to convert back from the UI data to the source data. In both cases, the current value and the type wanted for the converted data are passed to the method. To convert an Age Int32 into a Foreground Brush, we can implement whatever map- ping in the Convert function we feel comfortable with (see Example 6-19). In Example 6-19, in addition to deriving from IValueConverter, we’ve also applied the optional ValueConversion attribute. The ValueConversion attribute is useful for documenting the expected source and target types for developers and tools, but it is not enforced by WPF, so don’t expect it to catch values that don’t match the source or target types. The part that is required for our example is the implementation of Convert, where we hand out the brush that’s appropriate for the age being displayed. Because we haven’t provided any facility to change the Foreground brush being used to display the age, there’s no reason to do anything useful in the ConvertBack method—it won’t be called. I chose the name AgeToForegroundConverter because I have specific semantics I’m building into my converter class that go above simply converting an Int32 to a Brush. Even though this converter could be plugged in anywhere that converted an Int32 to a Brush, I might have very different requirements for a HeightToBackgroundConverter, for example. Once you’ve got a converter class, it’s easy to create an instance of one in the XAML, just like we’ve been doing with our Person object (see Example 6-20). Example 6-19. A simple value converter [ValueConversion(/*sourceType*/ typeof(int), /*targetType*/ typeof(Brush))] public class AgeToForegroundConverter : IValueConverter { // Called when converting the Age to a Foreground brush public object Convert(object value, Type targetType, ...) { // Only convert to brushes... if( targetType != typeof(Brush) ) { return null; } // DANGER! After 25, it's all downhill... int age = int.Parse(value.ToString( )); return (age > 25 ? Brushes.Red : Brushes.Black); } public object ConvertBack(object value, Type targetType, ...) { // Should not be called in our example throw new NotImplementedException( ); } } Data Binding | 187 In Example 6-20, once we have a named converter object in our XAML, we estab- lish it as the converter between the Age property and the Foreground brush by set- ting the Converter property of the binding object. Figure 6-10 shows the result of our conversion. In Figure 6-10, notice that as Tom’s age increases past the threshold, the converter switches the foreground brush from black to red. This change happens when the Age property changes. Because WPF detects the change, you do not need any explicit Example 6-20. Binding with a value converter ... ... Figure 6-10. A value converter in action (Color Plate 3) 188 | Chapter 6: Simple Data Binding code to force the color change, just as with any other kind of data binding. Notice also that the foreground color of the Birthday button matches the age text box’s color, because we’re using element binding to keep them in sync. Editable Value Conversion In addition to value conversion from the underlying data type to some other type for display, like our age-to-foreground-brush converter, you may also use value conver- sion for editing convenience. For example, although the Age property is automati- cally converted for us between an Int32 and a String in base 10, maybe your users would prefer base 16 (who wouldn’t?!). Enabling editing in base 16 is a matter of converting to and from a string in hexadecimal format, as shown in Example 6-21. Value Conversion Versus Type Conversion You may have noticed that until we decided to bring brushes into the mix, we didn’t need value converters at all. For example, with the Person class’s Age property in an Int32, we didn’t have to use a value converter even though the TextBox class’s Text property is of type String. This works because the Binding class uses the type converter support that’s been built into .NET since Version 1.0. Type converters work on a type basis (i.e., there’s a type converter that knows how to convert from integers to strings and back [and there are many more type converters as well]). This works because there is a reasonable general-purpose way to convert strings to integers (and vice versa). On the other hand, a value converter works on an application-specific basis. Although there is no built-in general-purpose conversion from integers to brushes, we can define an application-specific conversion to handle a certain kind of integer (e.g., ages, in our example) to brushes and apply that on a case-by-case basis. Example 6-21. A value converter for integers in base 16 public class Base16Converter : IValueConverter { public object Convert( object value, Type targetType, ...) { // Convert to base 16 return ((int)value).ToString("x"); } public object ConvertBack( object value, Type targetType, ...) { // Convert from base 16 return int.Parse( (string)value, System.Globalization.NumberStyles.HexNumber); } } Data Binding | 189 Hooking up this value converter works just like before, but this time we’re convert- ing the Text property of the TextBox instead of the Foreground property: Figure 6-11 shows the base-16 converter in action. One thing you’ll notice in our Base16Converter implementation of IValueConverter is that we haven’t guarded against a user entering something that can’t be interpreted as a hexadecimal number. If he does, the resulting exception is not handled by WPF, but is instead shown to the user as an unhandled exception. Although you can spend your time writing code to catch conversion errors, what you’ll really like to do is catch those errors before they ever get to the value converter, and instead communi- cate them to your users. For that, you’ll be best served by validation rules. Validation A validation rule is some code for validating a piece of data in the target before it’s used to update the source. The validation code is realized as an instance of a class that derives from the base ValidationRule class (from the System.Windows.Controls namespace) and overrides the Validate method. A built-in validation rule called ExceptionValidationRule (see Example 6-22) provides some measure of protection against a user intent on entering data outside the range supported by our age-to- foreground value converter. Figure 6-11. The base-16 value converter in action Example 6-22. Hooking up a validation rule ... 190 | Chapter 6: Simple Data Binding In Example 6-22, we’re using the shortcut markup extension binding syntax to bind the Foreground property to the Age (via the age-to-foreground value converter), but using the longhand syntax to bind the Text property to the Age so that we can create a list of validation rules. These validation rules will be executed in order when the target property changes. If they all succeed, the object is updated and everyone’s happy. If one of the rules fails, WPF highlights the offending data to make it easy to see what to fix, as shown in Figure 6-12. As nifty as the red outline around the offending text box is, it still doesn’t let the user know what’s wrong (i.e., the error message associated with the exception isn’t shown). To do that, we need to look under the hood a bit. When a validation result indicates invalid data, a ValidationError object is created that contains an object meant to describe the error, ideally for display by the UI. In the case of the ExceptionValidationRule, this “error content” object contains the ... Figure 6-12. A TextBox control highlighted as invalid (Color Plate 4) Example 6-22. Hooking up a validation rule (continued) Data Binding | 191 Message property of the Exception the validation rule catches. To gain access to those errors, you can listen to the ValidationError attached event, which you can set up as shown in Example 6-23. ... ... In Example 6-23, we’re calling the static AddErrorHandler method on the Validation class so that when a validation event happens on the age text box, we’ll get a notifi- cation. In that event handler, we can access the Error.ErrorContent property to get to the string provided by the validation rule. This event fires, however, only if the NotifyOnValidationError property is set to True on the Binding (the default is False). Example 6-23. Handling the ValidationError event with a message box // Window1.cs ... public Window1( ) { InitializeComponent( ); this.birthdayButton.Click += birthdayButton_Click; // Listen for the validation error event on the age text box // (you can do this in XAML by handling the Validation.Error // attached event on the ageTextBox) Validation.AddErrorHandler(this.ageTextBox, ageTextBox_ValidationError); } void ageTextBox_ValidationError( object sender, ValidationErrorEventArgs e) { // Show the string pulled out of the exception by the // ExceptionValidationRule MessageBox.Show( (string)e.Error.ErrorContent, "Validation Error"); } ... 192 | Chapter 6: Simple Data Binding With this event handler in place, we get our message box when there’s a validation error, as shown in Figure 6-13. And although “Input string was not in a correct format” is the message in the excep- tion that the Parse method of the Int32 class throws when there’s a parse error, I think we can do better—especially if we’d also like to set a range on the numbers that our users can enter for age.* Custom validation rules To make sure that our person’s age is within a certain range, we simply derive from the ValidationRule class and override the Validate method, as shown in Example 6-24. Figure 6-13. Handling the ValidationError event by showing a message box * On August 4, 1997, the world’s oldest person so far, Jeanne Louise Calment, died at age 122, having taken up fencing at age 85 and outlived the holder of her reverse-mortgage. Although I firmly believe that Ms. Cal- ment is showing us the way to a richer, longer life, it’ll be a while yet before we need the full range supported by the Int32 class (2,147,483,647 years young). Example 6-24. A custom validation rule public class NumberRangeRule : ValidationRule { int min; public int Min { get { return min; } set { min = value; } } int max; public int Max { get { return max; } set { max = value; } } Data Binding | 193 In this case, we’re creating a custom class with two public properties that describe the valid range of a number (specifically, an integer). The result of the validation is always an instance of the ValidationResult class. The most important part of the ValidationResult is the first argument to the constructor, which indicates whether the data is valid (true) or invalid (false). After that, we’re free to pass whatever we want as a CLR object. In our example, we check whether the string can be parsed into an integer and is within our range, passing back False and an error string if it’s not. Otherwise, we pass back True. (Because a valid result has little need for error detail, the ValidationResult class provides the static ValidResult property—a ValidationResult constructed by passing True and null—which you should use instead of creating a new ValidationResult object for a valid result.) To hook up our validation rule, we put it to the Binding object’s ValidationRules col- lection instead of the ExceptionValidationRule, as shown in Example 6-25. public override ValidationResult Validate( object value, System.Globalization.CultureInfo cultureInfo) { int number; if( !int.TryParse((string)value, out number) ) { return new ValidationResult( false, "Invalid number format"); } if( number < min || number > max ) { return new ValidationResult( false, string.Format("Number out of range ({0}-{1})", min, max)); } //return new ValidationResult(true, null); // valid result return ValidationResult.ValidResult; // static valid result // to save on garbage } } Example 6-25. Hooking up a custom validation rule Example 6-24. A custom validation rule (continued) 194 | Chapter 6: Simple Data Binding Now, when there’s a problem with the data, we get a message such as those shown in Figures 6-14 and 6-15. And so, although we now have nicer, more meaningful messages for our user when he enters invalid data, I am not a fan of the message box for validation error report- ing (it stops the very activity you’re trying to enable). Instead, I prefer a tool tip, as in Example 6-26. At first, this code works just peachy keen, as shown in Figure 6-16. Figure 6-14. A validation error from a custom validation rule Figure 6-15. Another validation error from a custom validation rule Example 6-26. Handling the ValidationError event with a tool tip void ageTextBox_ValidationError( object sender, ValidationErrorEventArgs e) { // Show the string created in NumberRangeRule.Validate ageTextBox.ToolTip = (string)e.Error.ErrorContent; } Data Binding | 195 When there’s a validation error, the message is shown in the tool tip on the control that’s holding invalid data. The problem is, once the user has corrected the data, the tool tip continues to hang around, as shown in Figure 6-17. Unfortunately, there’s no ValidationSuccess event that lets us clear the error message from the tool tip. What we really want is to update the tool tip based on the changing validation error data, whether it’s in error or success, which sounds like a job for data binding. However, before we can do that, we need to take a closer look at Path syntax. Binding Path Syntax When you use Path=Something in a Binding statement, the Something can be in a num- ber of formats, including the following commonly used variants:* Path=Property Bind to the property of the current object, whether the property is a CLR prop- erty, a dependency property, or an attached property (e.g., Path=Age). Path=(OwnerType.AttachedProperty) Bind to an attached dependency property (e.g., Path=(Validation.HasError)). Figure 6-16. Handling the ValidationError event by setting a tool tip Figure 6-17. The tool tip hanging around after the validation error has been resolved * The Windows Platform SDK has a more complete list of the WPF binding path syntax variants, including escaping rules, on a page titled “Binding Declarations Overview,” available at http://msdn2.microsoft.com/ en-us/library/ms752300.aspx#Path_Syntax (http://tinysells.com/65). 196 | Chapter 6: Simple Data Binding Path=Property.SubProperty Bind to a subproperty (or a sub-subproperty, etc.) of the current object (e.g., Path=Name.Length). Path=Property[n] Bind to an indexer (e.g., Path=Names[0]). Path=Property/Property Master-detail binding, described later (e.g., Path=Customers/Orders). Path=(OwnerType.AttachedProperty)[n].SubProperty Bind to a mixture of properties, subproperties, and indexers (e.g., Path=(Validation.Errors)[0].ErrorContent). So far, we’ve been using the Path=Property syntax, but if we want to get at an error on the validation errors collection, we’ll need to use a mixed path that includes an attached property, an indexer, and a subproperty, as shown in Example 6-27. In Example 6-27, the tool tip has been bound to the first error from the attached property Errors collection. When there are no errors, the tool tip is empty. When there is an error, the ErrorContent property (which, you’ll recall, we pack with an error string in NumberRangeRule.Validate) is used to populate the tool tip. We no longer need to set the NotifyOnValidationError property or handle the ValidationError event because as the Errors collection changes, the binding makes sure that the tool tip is kept up-to-date. In other words, we use data binding to the ToolTip property on the age text box to report a validation error on the Text prop- erty. When the collection of errors is null, the binding engine will automatically null out the tool tip, giving us the empty tool tip on success that we so deeply desire. Example 6-27. Binding the ToolTip property to the validation error message Data Binding | 197 Relative Sources One thing you may find a bit onerous in Example 6-27 is the use of the explicit ElementName to bind to another part of the target as the data source. Wouldn’t it be nicer if you could just say, “Bind to myself, please?” And in some cases, you may not have a name for the thing to which you’d like to bind (e.g., to fulfill queries like “Bind to the Border that’s the parent or grandparent [or great-grandparent] of me” or even “Bind to the previous bit of data in the list instead of the current bit of data”). All of these are available with the use of the RelativeSource property of a binding, shown in Example 6-28. In Example 6-28, we’re using the Self designator to use the TextBox currently serving as the data binding UI target as the data source, so that we can bind to the validation errors collection associated with it to compose the tool tip. For more information about Self and the other relative sources—FindAncestor, Previous, and TemplatedParent (which is also discussed in Chapter 9)—I recommend the SDK documentation.* Update Source Trigger If you’ve been following along, you may have noticed that validation, and therefore the pushing of the updated data into the underlying object, doesn’t happen until the age text box loses focus. On the other hand, you may decide that you’d like validation et al. to happen immediately when the control state changes, long before the focus is lost. This behavior is governed by the UpdateSourceTrigger property on the Binding object: namespace System.Windows.Data { public enum UpdateSourceTrigger { Default = 0, // updates "naturally" based on the target control PropertyChanged = 1, // updates the source immediately LostFocus = 2, // updates the source when focus changes Explicit = 3, // must call BindingExpression.UpdateSource() } } The default value of UpdateSourceTrigger is UpdateSourceTrigger.Default, which means that the trigger for updating the data source is based on the target property (e.g., the trigger for the Text property of the TextBox is LostFocus). If you’d like to force another kind of behavior, you can set it on the Binding, as shown in Example 6-29. Example 6-28. Using a RelativeSource * A good place to continue your exploration of relative sources is the “RelativeSourceMode Enumeration” page in the Windows Platform SDK, which is available at http://msdn2.microsoft.com/en-us/library/ system.windows.data.relativesourcemode.aspx (http://tinysells.com/66). 198 | Chapter 6: Simple Data Binding In this case, instead of waiting for the focus to be lost to do validation, it happens on each character entered. Debugging Data Binding You may have noticed that our age text box’s binding options have gotten fairly involved: There’s a lot going on here and it would be easy to get some of it wrong. For exam- ple, if we had a background in journalism, we might have used one-based indexing instead of zero-based indexing to access the first error in our list of validation errors when setting up the binding for the tool tip: ... In this case, as in most others, the WPF data binding engine will simply swallow the error so as not to disturb our user friends.* So, how are we to find it? Well, you need only check the debug output to see the error shown in Example 6-30, and all will be revealed. Example 6-29. Changing the update source trigger ... * The swallowing of errors like these lets us declare data bindings before the data is actually available, simpli- fying our programming chores considerably in this area. Where Are We? | 199 In this case, we can see that the index is out of range, giving us a clue as to how to fix it. The data binding debug output provides all kinds of helpful hints like this, and you should check it if eyeballing your data binding expressions doesn’t yield the source of the issue.* Where Are We? Data binding is about keeping two values synchronized. One value, the target, is a dependency property, typically on a UI element. The other, the source, is a CLR property—the result of an XPath expression, a dependency property, or a dynamic property used by objects like those provided by ADO.NET that don’t know what the data is going to be until runtime. By default, as either the target or the source changes, the other value is updated, but you can control that with the alternate bind- ing modes (e.g., one-way, one-time, etc.). As data changes, type conversion happens automatically if a converter is available, although you can take full control of the conversion and validation process if you so choose, doing things like restricting data ranges and converting data formats, or even automatically showing errors in tool tips. You might think that WPF data binding is powerful with these features, and you’d be right, but we’ve just touched on the bare essentials associated with bindings to prop- erties on a single object. When you’ve got a list of objects as your data source, you’ve got all kinds of other facilities, which is the subject of the next chapter. Example 6-30. Watch debug output for help debugging data binding problems System.Windows.Data Error: 12 : Cannot get '' value (type 'ValidationError') from '(Validation.Errors)' (type 'ReadOnlyObservableCollection`1'). BindingExpression:Path=(0).[1].ErrorContent; DataItem='TextBox' (Name='ageTextBox'); target element is 'TextBox' (Name='ageTextBox'); target property is 'ToolTip' (type 'Object') TargetInvocationException: 'System.Reflection.TargetInvocationException: Exception has been thrown by the target of an invocation. ---> System.ArgumentOutOfRangeException: Index was out of range. Must be non-negative and less than the size of the collection. Parameter name: index * For more on the data binding debug output, see the SDK documentation for the PresentationTraceSources class at http://msdn2.microsoft.com/en-us/library/system.diagnostics.presentationtracesources.aspx or http:// tinysells.com/79 and Mike Hillberg’s most excellent blog posting on this subject at http://blogs.msdn.com/ mikehillberg/archive/2006/09/14/WpfTraceSources.aspx or http://tinysells.com/78. 200 Chapter 7CHAPTER 7 Binding to List Data 7 In Chapter 6, we looked at the basics of data binding with respect to single objects. However, when you’ve got lists of objects, you’ve got still more flexibility and power, including managing the “current” object in a list, sorting, filtering, and grouping. Also, WPF gives you the ability to expand a single data source object into a set of target UI elements with data templates, bring in XML and relational data, and perform master- detail binding and hierarchical binding. We discuss all of these topics in this chapter. Binding to List Data To kick things off, recall our Person class from Chapter 6; let’s add a new type for keeping track of a list of Person objects (see Example 7-1). Example 7-1. Declaring a custom list type using System.Collections.Generic; // List ... namespace PersonBinding { public class Person : INotifyPropertyChanged { // INotifyPropertyChanged Members public event PropertyChangedEventHandler PropertyChanged; protected void Notify(string propName) { if( this.PropertyChanged != null ) { PropertyChanged(this, new PropertyChangedEventArgs(propName)); } } string name; public string Name { get { return this.name; } set { if( this.name == value ) { return; } this.name = value; Notify("Name"); } } Binding to List Data | 201 We can bind this new list data source in exactly the same way as if we were binding to a single object data source (see Example 7-2). int age; public int Age { get { return this.age; } set { if(this.age == value ) { return; } this.age = value; Notify("Age"); } } public Person( ) {} public Person(string name, int age) { this.name = name; this.age = age; } } // Create an alias for a generic type so that we can // create a list of Person objects in XAML class People : List {} ... } Example 7-2. Declaring a collection in XAML ... Name: Example 7-1. Declaring a custom list type (continued) 202 | Chapter 7: Binding to List Data In Example 7-2, we’ve created an instance of the People collection and populated it with three Person objects. Running it will look just like running the Person object ver- sion from Chapter 6 (Figure 7-1). Even though we’re binding to a list of Person objects, each TextBlock can be bound to a property from only a single Person object. Current Item While the text box properties can be bound to only a single object at a time, the binding engine is giving them the current item in the list of possible objects they could bind against, as illustrated in Figure 7-2. By default, the first item in the list is the initial current item. Because the first item in our list example is the same as the only item to which we were binding before, things look and act in exactly the same way as our single Person object example, except for the Birthday button. Figure 7-1. Showing one person at a time from a list Figure 7-2. Binding to a list data source Binding Name TextBox .Text Person .Name = "Tom" .Age = 11 Binding Age TextBox .Text Person .Name = "John" .Age = 12 People Binding to List Data | 203 Getting the current item Recall the current Birthday button click event handler from Chapter 6 (see Example 7-3). Our Birthday button has always been about celebrating the birthday of the current person, but so far the current person has always been the same, so we could just shortcut things and go directly to a single Person object. Now that we’ve got a list of objects, this no longer behaves acceptably (unless you consider an unhandled excep- tion message box acceptable behavior). Further, pulling the collection out of the resources won’t tell us which Person is currently being shown in the UI, because it has no idea about such things (nor should it). For this information, we’re going to have to go to the broker between the data bound control and the collection of items, the collection view. The job of the collection view (or just “view”) is to provide services on top of the data, including sorting, filtering, grouping, and, most important for our purposes at the moment, control of the current item. A view is an implementation of a data-specific interface which, in our case, is going to be the ICollectionView interface. We can access a view over our data with the static GetDefaultView method of the CollectionViewSource class, as shown in Example 7-4. Example 7-3. Finding a custom object declared in XAML public partial class Window1 : Window { ... void birthdayButton_Click(object sender, RoutedEventArgs e) { Person person = (Person)this.FindResource("Tom")); ++person.Age; MessageBox.Show(...); } } Example 7-4. Getting a collection’s view public partial class Window1 : Window { ... void birthdayButton_Click(object sender, RoutedEventArgs e) { // Get the current person out of the collection view People people = (People)this.FindResource("Family"); ICollectionView view = CollectionViewSource.GetDefaultView(people); Person person = (Person)view.CurrentItem; ++person.Age; MessageBox.Show(...); } } 204 | Chapter 7: Binding to List Data To retrieve the view associated with the Family collection, Example 7-4 makes a call to the GetDefaultView method of CollectionViewSource, which provides us with an implementation of the ICollectionView interface associated with our bound data col- lection. Our collection happens to have been created in a resource, but that doesn’t matter to the GetDefaultView method; it only maps a bound collection to its associ- ated view. With the collection view, we can grab the current item, cast it into an item from our collection (the CurrentItem property returns an object), and use it for display. Navigating between items In addition to getting the current item, we can also change which item is current using the MoveCurrentTo methods of the ICollectionView interface, as shown in Example 7-5. Example 7-5. Navigating between items via the view public partial class Window1 : Window { ... ICollectionView GetFamilyView( ) { People people = (People)this.FindResource("Family"); return CollectionViewSource.GetDefaultView(people); } void birthdayButton_Click(object sender, RoutedEventArgs e) { ICollectionView view = GetFamilyView( ); Person person = (Person)view.CurrentItem; ++person.Age; MessageBox.Show(...); } void backButton_Click(object sender, RoutedEventArgs e) { ICollectionView view = GetFamilyView(); view.MoveCurrentToPrevious(); if( view.IsCurrentBeforeFirst ) { view.MoveCurrentToFirst(); } } void forwardButton_Click(object sender, RoutedEventArgs e) { ICollectionView view = GetFamilyView(); view.MoveCurrentToNext(); if( view.IsCurrentAfterLast ) { view.MoveCurrentToLast(); } } } Binding to List Data | 205 The ICollectionView methods MoveCurrentToPrevious and MoveCurrentToNext change which item is currently selected by going backward and forward through the collec- tion. If we walk off the end of the list in one direction or the other, the IsCurrentBeforeFirst or IsCurrentAfterLast property will tell us that. The MoveCurrentToFirst and MoveCurrentToLast help us recover after walking off the end of the list, and would be useful for implementing the Back and Forward buttons shown in Figure 7-3 as well as the First and Last buttons (this is an opportunity for you to apply what you’ve learned...). Figure 7-3 shows the effect of moving forward from the first Person in the collection, including the color changing based on the Person object’s Age property (which still works in exactly the same way). List Data Targets Of course, we can push the user of list data only so far without providing him with a control that can actually show more than one item at a time—like the ListBox con- trol, shown in Example 7-6. Figure 7-3. Navigating between items in a list data source (Color Plate 5) Example 7-6. Binding a list element to a list data source ... ... ... 206 | Chapter 7: Binding to List Data In Example 7-6, the ItemsSource property of the ListBox is a Binding with no path, which is the same as saying “bind to the entire current object.” Notice that there’s no source, either, so the binding works against the first data context it finds that is set. In this case, the first set data context is the one from the Grid, the same one as shared between both the name and the age text boxes. Also, we’re setting the ListBox class’s IsSynchronizedWithCurrentItem property to True so that as the selected item of the listbox changes, it updates the current item in the view (and vice versa).* With our ItemsSource binding in place, we should expect to see all three Person objects in the listbox, as shown in Figure 7-4. As you might have noticed, everything is not quite perfect in Figure 7-4. When you bind against an object, data binding does its best to display it. Without special instructions, it’ll use a type converter to get a string representation (falling back on the ToString method when all else fails). In the case of both the Name and Age proper- ties, the built-in conversions give us a string representation that works well for our purposes. However, the Person object provides no special instructions, so the fall- back does nothing but show the name of the type. Name: ... * By default, listboxes do not synchronize with the current item for reasons I have yet to fathom... Figure 7-4. Person objects being displayed in a ListBox without help Example 7-6. Binding a list element to a list data source (continued) Binding to List Data | 207 Because binding uses an object’s ToString method if it has nothing else, you may feel tempted to add a ToString method as a way to decide how your data objects look in your WPF UIs. You should avoid this temptation, for at least the following reasons: • It is impossible to provide a string representation of a data object that would be appropriate for every way that you might like to display it. • You lose all kinds of flexibility in how to display a data object if all you have is the whole thing represented as a string (e.g., maybe you’d like some of it bold or some of it as the content of a Button). • There’s no way to fire a notification to WPF such that it will auto- matically pull in the new data object’s data as it changes when ToString is used, giving you a single, static view. Display Members, Value Members, and Look-Up Bindings If you want to show only one of the properties, the ListBox class (and the rest of the ItemsControl-derived controls—e.g., Menu, ListBox, ListView, ComboBox, TreeView, etc.) provides the DisplayMemberPath property: This at least gives us part of the data, as you can see in Figure 7-5. In addition to the path describing the data to display, the ItemsControl class pro- vides a path to describe the selected value of a piece of data: Figure 7-5. The DisplayMemberPath in action 208 | Chapter 7: Binding to List Data The SelectedValue is exposed from the ItemsControl as an application-defined way to separate the data from what’s displayed. By default, the SelectedValue, the SelectedItem, and the object used to construct the item at that spot in the list are all the same (e.g., a Person if we hadn’t changed it by setting the SelectedValuePath). This data is often used when the selection changes or an item is double-clicked: void lb_MouseDoubleClick(object sender, MouseButtonEventArgs e) { int index = lb.SelectedIndex; if( index < 0 ) { return; } Person item = (Person)lb.SelectedItem; int value = (int)lb.SelectedValue; // Age // Do something profitable with this data ... } The difference between display value and selected value becomes especially interest- ing when you want to do something like a combo box with friendly names (e.g., sales- person name), but key off of opaque values in the real data (e.g., salesperson ID). For example, if we wanted to provide a UI that mapped ages represented in scary numbers to soothing phrases, we could construct a NamedAge type for use in populat- ing a look-up table, as shown in Example 7-7. Example 7-7. A helper for populating a look-up table public class NamedAge : INotifyPropertyChanged { // INotifyPropertyChangeIdINotifyPropertyChanged Members public event PropertyChangedEventHandler PropertyChanged; protected void Notify(string propNameForAge) { if( this.PropertyChanged != null ) { PropertyChanged(this, new PropertyChangedEventArgs(propNameForAge)); } } string nameForAge; public string NameForAge { get { return this.nameForAge; } set { if( this.nameForAge == value ) { return; } this.nameForAge = value; Notify("NameForAge"); } } int ageId; public int AgeId { get { return this.ageId; } set { if( this.ageId == value ) { return; } this.ageId = value; Notify("AgeId"); Binding to List Data | 209 Now we can populate the table for looking up an age’s name from its number, as in Example 7-8. This handy table is all we need to replace the TextBox for entering hard-to-format- correctly ages into an easy-to-use combo box with all of the values filled in for us, as shown in Figure 7-6. OK, obviously this particular example isn’t useful, but mapping IDs to names is something we want to do all the time in data binding applications. To get our combo box to show the list of available options, we need to bind the set of options to our look-up table, setting the display and value members appropriately, as in Example 7-9. } } } class NamedAges : ObservableCollection { } Example 7-8. A look-up table suitable for binding ... Figure 7-6. Data binding to a look-up table Example 7-7. A helper for populating a look-up table (continued) 210 | Chapter 7: Binding to List Data Example 7-9 tells the combo box where the possible choices come from (the NamedAgeLookup table), which property to show the user (the NameForAge property), and which property is the real value (the AgeId property). The final step is the bit of binding that tells the combo box where to get the currently selected value (in terms that match our selected value path; in other words, Age), as in Example 7-10. Just as before, where the TextBox object’s Text property was set to bind to the Age prop- erty of the currently selected Person, so is the ComboBox object’s SelectedValue property set. As the display value changes (due to interaction with the user), the selected value is updated, as is the underlying Age. Likewise, as the Age changes (like when the Birthday button is clicked), the binding synchronizes the selected value, causing the value displayed in the combo box to change. All of this is very handy, but in our case, we don’t really want to have named ages, nor do we want to display a single property in the ListBox for each Person object it displays. Data Templates If you want to show more than one property from a custom class or mix things up with more than just a plain TextBlock object (which is all the DisplayMemberPath gives you), you want a data template. A data template is a tree of elements to expand in a particular context. For example, for each Person object, you might like to be able to concatenate the name and age together in a string like the following: John (age:12) We can think of this as a logical template that looks like this: Name (age:Age) To define this template for items in the listbox, we create a DataTemplate element, as shown in Example 7-11. Example 7-9. Data binding to a look-up table Example 7-10. Binding the look-up table to the selected value Example 7-11. Using a data template Binding to List Data | 211 In this case, the ListBox control has an ItemTemplate property, which accepts an instance of the DataTemplate class. The DataTemplate allows us to specify a single child element to repeat for every item that the ListBox control binds against (although that child can have any number of children of its own, and so on). In our case, we’re using a TextBlock to gather together some hardcoded text and two nested TextBlock controls for text bound to properties on each Person object. Notice that we’re also binding the Foreground to the Age property using the age-to-foreground value converter so that Age properties show up black or red consistently between the listbox and age text box. With the use of the data template, our experience goes from Figure 7-4 to Figure 7-7. Notice that the listbox shows all the items in the collection and keeps the view’s idea of the current item synchronized with it as the selection moves or the back and for- ward buttons are clicked (actually, you can’t really “notice” this based on the screen- shot, but trust me, that’s what happens). In addition, as data changes on Person objects, the listbox and the text boxes are all kept in sync, including the Age color. (age: ) Figure 7-7. Person objects being displayed in a ListBox with a data template (Color Plate 6) Example 7-11. Using a data template (continued) 212 | Chapter 7: Binding to List Data Typed data templates In Example 7-11, we explicitly set the data template for items in our listbox by creat- ing the DataTemplate inside the ListBox.ItemTemplate element. Using this technique, if a Person object shows up in a button or in some other element, we’d have to spec- ify the data template for those Person objects separately. On the other hand, if you’d like a Person object to have a specific template no matter where it shows up, you can do so with a typed data template, as shown in Example 7-12. In Example 7-12, we’ve hoisted the data template definition into a resources block and tagged it with a type using the DataType property.* Now, unless told otherwise, whenever an element using the WPF content model† sees an instance of the Person object within the scope of the data template, it will apply the appropriate data tem- plate. This is a handy way to make sure that data shows in a consistent way through- out your application without worrying about just where it shows. DataTemplates and the DataContext You’ll notice in Example 7-12 that we’re not setting the Source property. As you saw in the preceding chapter, this means that the Binding object will use the DataContext as its source. You should also recall (from the preceding chapter) that the DataContext uses the dependency property inheritance mechanism to travel the element tree to find its value if it’s not set explicitly (i.e., if the element we’re binding has no explicit DataContext set, we’ll use the one from the parent or the grandparent, etc.). Example 7-12. A typed data template ... ... (age: ) ... ... * If you’ve skipped ahead to Chapter 12, you know that all resources, without exception, must have a Key. Cer- tain resource types have a property that, when set, also sets the Key implicitly. In the case of the DataTemplate, setting the DataType property also sets the Key property. † As you’ll recall from Chapter 1, the content model of WPF allows you to put arbitrary content into most ele- ments (e.g., Button supports the content model whereas the TextBox doesn’t). Binding to List Data | 213 However, if you’ll look at the where the binding takes place on the text blocks inside the data template in Example 7-12, you’ll notice that we don’t actually want the data context to be on the parent of the DataTemplate, but rather to be on the individual elements of the items source on the listbox where our Person data template is being expanded. To enable the bindings in our data template to work as we expect the template expansion engine in WPF will set the DataContext property of the root of each element tree that it expands. For instance, in our example, we’ve got three Person objects, so you can think of the logical expansion of the data template inside the listbox as shown in Example 7-13. As the data template is expanded for each item in the list referenced by the ItemsSource property, the data context is set to the individual item so that when the Binding objects are looking for their data sources, they find the data context of the element at the root of the expanded data template, like the top-level TextBlock in our example. Not only does this explain how data bindings work inside data templates, but also this is something we can use. For example, we need to use the DataContext property if we want to handle events on objects inside the data template and figure out which data object was used to expand the template, as shown in Example 7-14. Example 7-13. Logical expansion of the Person data template (age: ) (age: ) (age: ) Example 7-14. DataTemplates and the DataContext ... ... ... (age: ) 214 | Chapter 7: Binding to List Data In this example, we added a Button to the data template with a Click event handler. When the button is clicked, the event handler’s sender argument is the Button that was generated when the template was expanded. Because of dependency property inheritance, the DataContext property on the Button gets the same value of the DataContext property on the root TextBlock in the DataTemplate for the item in the list of Person objects that was used to populate the ListBox. Figure 7-8 shows the results of clicking one of the Show buttons. In Figure 7-8, the Show button in the second row was clicked, which means that the same Person object in the second row of the listbox is the one that is available in the DataContext on the Show button in that row. ... ... // Window1.xaml.cs ... void showButton_Click(object sender, RoutedEventArgs e) { // Get the button generated by the data template expansion Button showButton = (Button)sender; // Get the person associated with the generated button via the data context Person person = (Person)showButton.DataContext; // Do something with that person... MessageBox.Show(string.Format("{0} is {1} years old", person.Name, person.Age)); } ... Figure 7-8. Using the DataContext associated with an expanded DataTemplate Example 7-14. DataTemplates and the DataContext (continued) Binding to List Data | 215 List Changes Thus far, we’ve got a list of objects that we can edit in place and navigate among, even highlighting certain data values with ease and providing an automatic look for data that wasn’t shipped with a rendering from the manufacturer. In the spirit of how far we’ve come, you might suspect that implementing an Add button, as in Example 7-15, would be a breeze. The problem with this code is that although the collection view associated with our list data source can figure out the existence of a new item as you move to it, the listbox itself has no idea that something new has been added, as shown in Figure 7-9. In interacting with the state of the application shown in Figure 7-9, I ran the applica- tion, clicked the Add button, and used the Forward button to navigate to it. How- ever, just as data bound objects need to implement the INotifyPropertyChanged interface, data bound lists need to implement the INotifyCollectionChanged inter- face* (see Example 7-16). Example 7-15. Adding an item to a data bound collection public partial class Window1 : Window { ... void addButton_Click(object sender, RoutedEventArgs e) { People people = (People)this.FindResource("Family"); people.Add(new Person("Chris", 37)); } } Figure 7-9. The ListBox doesn’t know the collection has gotten bigger * Again, this is not really a requirement; WPF data works even if the collection doesn’t implement INotifyCollectionChanged, although it won’t know about changes to the collection. If you have to integrate with collections that don’t implement this interface, or the .NET 1.x version of this interface—IBindingList (which WPF still supports)—you’ll need to fall back on the manual updating technique mentioned in Chap- ter 6 (i.e., BindingExpression.UpdateTarget). 216 | Chapter 7: Binding to List Data The INotifyCollectionChanged interface is used to notify the data bound control that items have been added or removed from the bound list. Although it’s common to imple- ment INotifyPropertyChanged in your custom types to enable two-way data binding on your type’s properties, it’s less common to implement your own collection classes, which leaves you less opportunity to implement the INotifyCollectionChanged inter- face. Instead, you’ll most likely be relying on one of the collection classes in the .NET Framework Class Library to implement INotifyCollectionChanged for you. The num- ber of such classes is small and unfortunately, List, the collection class we’re using to hold Person objects, is not among them. Although you’re more than wel- come to spend your evenings and weekends implementing the INotifyCollectionChanged interface, including hooking all of the methods that change whatever base collection you use as a helper, WPF provides the ObservableCollection class, shown in Example 7-17, for those of us with more pressing duties. Because ObservableCollection derives from Collection and implements the INotifyCollectionChanged interface, we can use it instead of List for our Person collection (see Example 7-18). Now, when an item is added to or removed from the Person collection, those changes will be reflected in the list data bound controls, as shown in Figure 7-10. Example 7-16. The INotifyCollectionChanged interface namespace System.Collections.Specialized { public interface INotifyCollectionChanged { event NotifyCollectionChangedEventHandler CollectionChanged; } } Example 7-17. WPF’s implementation of INotifyCollectionChanged namespace System.Collections.ObjectModel { public class ObservableCollection : Collection, INotifyCollectionChanged, INotifyPropertyChanged { ... } } Example 7-18. ObservableCollection in action using System.ComponentModel; // INotifyPropertyChanged using System.Collections.ObjectModel; // ObservableCollection ... class Person : INotifyPropertyChanged {...} class People : ObservableCollection {} ... Binding to List Data | 217 Here, we’ve clicked the Add button and clicked on the new Person object that the listbox displayed for us (a newly added item in a collection does not become the selected item automatically). Sorting Once we have data targets showing more than one thing at a time properly, a per- son’s fancy turns to, well, fancier things, like sorting the view of the data, filtering the data out of the view, or grouping related data. Recall that the view always sits between the data bound target and the data source. The view allows us to do a num- ber of things to the data before it’s displayed, including changing the order in which the data is shown (a.k.a. sorting). The simplest way to sort is by manipulating the SortDescriptions property of the view, as shown in Example 7-19. Figure 7-10. Keeping the ListBox in sync with INotifyCollectionChanged Example 7-19. Sorting public partial class Window1 : Window { ... ICollectionView GetFamilyView( ) { People people = (People)this.FindResource("Family"); return CollectionViewSource.GetDefaultView(people); } void sortButton_Click(object sender, RoutedEventArgs e) { ICollectionView view = GetFamilyView( ); if( view.SortDescriptions.Count == 0 ) { view.SortDescriptions.Add( new SortDescription("Name", ListSortDirection.Ascending)); view.SortDescriptions.Add( new SortDescription("Age", ListSortDirection.Descending)); } 218 | Chapter 7: Binding to List Data Here we’re toggling between sorted and unsorted views by checking the SortDescriptionCollection exposed by the ICollectionView SortDescription prop- erty. If there are no sort descriptions, we sort first by the Name property in ascending order, then by the Age property in Descending order. If there are sort descriptions, we clear them, restoring the order to whatever it was before we applied our sort. While the sort descriptions are in place, any new objects added to the collection will be dis- played in their proper sort position by the view, as Figure 7-11 shows. A collection of SortDescription objects should cover most cases, but if you’d like a bit more control, you can provide the view with a custom sorting object by imple- menting the IComparer interface from the System.Collections namespace,* as shown in Example 7-20. else { view.SortDescriptions.Clear(); } } } Figure 7-11. Unsorted on the left and sorted on the right * Unfortunately, WPF doesn’t use the generic IComparer interface from the System.Collections.Generic namespace. Example 7-20. Custom sorting class PersonSorter : IComparer { public int Compare(object x, object y) { Person lhs = (Person)x; Person rhs = (Person)y; Example 7-19. Sorting (continued) Binding to List Data | 219 In the case of setting a custom sorter, we cast the result of GetDefaultView to a ListCollectionView, which is what WPF wraps around an implementation of IList (which our ObserverableCollection provides) to provide view functionality. There are other implementations of ICollectionView that don’t provide custom sorting, so you’ll want to test this code before shipping it.* Default Collection Views The SDK documentation for the individual views will tell you how each different kind of collection data is mapped to a default view, but Table 7-1 is a handy guide to help you along. // Sort Name ascending and Age descending int nameCompare = lhs.Name.CompareTo(rhs.Name); if( nameCompare != 0 ) return nameCompare; return rhs.Age - lhs.Age; } } public partial class Window1 : Window { ... ICollectionView GetFamilyView( ) { People people = (People)this.FindResource("Family"); return CollectionViewSource.GetDefaultView(people); } void sortButton_Click(object sender, RoutedEventArgs e) { ListCollectionView view = (ListCollectionView)GetFamilyView( ); if( view.CustomSort == null ) { view.CustomSort = new PersonSorter(); } else { view.CustomSort = null; } } } * Hopefully, you’ll test the rest of your code before shipping it, too, but it never hurts to point these things out... Table 7-1. The default views for each collection data type Collection data Default view IEnumerable CollectionView IList ListCollectionView IBindingList BindingListCollectionView Example 7-20. Custom sorting (continued) 220 | Chapter 7: Binding to List Data If you don’t like the view that WPF provides, you can create your own implementa- tion of ICollectionView and bind to that, too. In fact, this is handy for “stacking” views, that is, using one view as the input to another—when you need to implement custom views for features that WPF doesn’t support out of the box (like “top N” functionality). Filtering Just because all of the objects are shown in an order that makes you happy doesn’t mean that you want all of the objects to be shown. For those rogue objects that hap- pen to be in the data but that shouldn’t be displayed, we need to feed the view an implementation of the Predicate delegate* that takes a single object parame- ter and returns a Boolean indicating whether the object should be shown (see Example 7-21). Like sorting, with a filter in place, new things are filtered appropriately, as Figure 7-12 shows. The top window in Figure 7-12 shows no filtering, the middle window shows filter- ing of the initial list, and the bottom window shows adding a new adult with filtering still in place. * Unlike sorting, which uses a single method interface implementation because of history, filtering uses a generic delegate because the addition of anonymous delegates and generics to C# 2.0 has made them all the rage. Example 7-21. Filtering public partial class Window1 : Window { ... ICollectionView GetFamilyView( ) { People people = (People)this.FindResource("Family"); return CollectionViewSource.GetDefaultView(people); } void filterButton_Click(object sender, RoutedEventArgs e) { ICollectionView view = GetFamilyView( ); if( view.Filter == null ) { view.Filter = delegate(object item) { // Just show the over 25-year-olds return ((Person)item).Age >= 25; }; } else { view.Filter = null; } } } Binding to List Data | 221 Grouping Grouping is just what it sounds like—displaying data based on some criteria in a named group. The grouping criteria can be anything you like, but the only criterion that comes with WPF out of the box is grouping by property values. As we’ll see, this one is pretty darn flexible, so you’ll rarely need anything else. You have to do two things to set up grouping. The first is to establish the groups you’d like to use, which you do by manipulating the GroupDescriptions collection on your view (see Example 7-22). Figure 7-12. Unfiltered, filtered for adults, and adding to a filtered view 222 | Chapter 7: Binding to List Data The PropertyGroupDescription object takes the name of the property you’d like to use for grouping. The groups themselves will be composed of all of the unique values pulled from the designated property on the items in the collection (e.g., Tom: 11, John: 12, Melissa: 38, and Penny: 38 will yield three named groups based on age: 11, 12, and 38). All classes that derive from ItemsControl can display items in groups. To exploit this, we need to provide the control with a group style. A group style is not related to a normal style (as introduced in Chapter 1 and explored in depth in Chapter 8), but is rather a collection of group visualization-related information, like the real style for the container,* the data template for the header, and whether to hide empty groups. A group style is an instance of the GroupStyle class, and ItemsControl objects won’t group data visually without one. Luckily, the GroupStyle class itself provides a static Default property that exposes a group style that works nicely to get us started, which we can use as shown in Example 7-23.† Example 7-22. Establishing data groups public partial class Window1 : Window { ... ICollectionView GetFamilyView( ) { People people = (People)this.FindResource("Family"); return CollectionViewSource.GetDefaultView(people); } void groupButton_Click(object sender, RoutedEventArgs e) { ICollectionView view = GetFamilyView( ); if( view.GroupDescriptions.Count == 0 ) { // Group by age view.GroupDescriptions.Add(new PropertyGroupDescription("Age")); } else { view.GroupDescriptions.Clear(); } } } * The ItemsControl generates containers for group items in the same way as the “item container generation” mechanism described in Chapter 5. † Unfortunately, the XAML compiler won’t accept the standard shortcut syntax, but the long syntax works just fine. For the curious, the XAML compiler is having trouble because the CLR GroupStyle property, defined on the ItemsControl base class, is defined as a read-only collection, even though the underlying dependency property is read-write. Example 7-23. Using the default group style Binding to List Data | 223 This group style shows the name of the group above each indented group, as shown in Figure 7-13. The data template used in the default group style shows the Name of the CollectionViewGroup constructed to reference the items in each group. If you’d like to replace that data template with one that includes custom formatting of group name data or other information from the CollectionViewGroup object (like the num- ber of items in the group), you can do so with a custom data template, as shown in Example 7-24. Figure 7-13. Grouping with the default group style Example 7-24. A custom group style () 224 | Chapter 7: Binding to List Data Here we’ve set the header template of the group style to a data template containing a TextBlock with a black background, a white foreground, and two nested TextBlock objects, one to display the name of the group and another to display the number of items, as Figure 7-14 shows. Figure 7-14 shows grouping by each possible value of the Age property in all of the data, automatically indenting the data in each group. Taking it one step further, what if we’d like to group by ranges—say, over and under 25. If we wanted, we could derive from the GroupDescription, overriding the GroupNamesFromItem method to classify items as belonging to one or more groups. (The PropertyGroupDescription class derives from GroupDescription, as do all data grouping policy implementations.) However, the PropertyGroupDescription class itself provides this flexibility by allow- ing for a custom IValueConverter implementation that groups items without the need for a custom GroupDescription class. Example 7-25 shows a value converter that con- verts values from the Age property into groups. Figure 7-14. A custom group style in action Example 7-25. A custom value converter for grouping public class AgeToRangeConverter : IValueConverter { public object Convert(object value, Type targetType, ...) { return (int)value < 25 ? "Under the Hill" : "Over the Hill"; } public object ConvertBack(object value, Type targetType, ...) { // should not be called in our example throw new NotImplementedException( ); } } Binding to List Data | 225 This code assumes that the PropertyGroupDescription class will take each Person object and pass in the Age property, giving us the opportunity to group the data into our two buckets. We can configure the PropertyGroupDescription object to do this by passing the value converter to the constructor, as in Example 7-26. Figure 7-15 shows the results. In fact, the use of the converter is so flexible that it’s hard to imagine needing to implement a custom group description at all. If you want an object to belong to more than one group, you can return a collection of group names from the Convert method instead of a single name. If you want to get at all of the object’s data instead of just a single property, you can construct a PropertyGroupDescriptor with null as the prop- erty name, which will pass in the entire object as the value parameter to Convert instead of just a single property’s data. Finally, if you want to have control over the Example 7-26. Using a custom value converter for grouping void groupButton_Click(object sender, RoutedEventArgs e) { ICollectionView view = GetFamilyView( ); if( view.GroupDescriptions.Count == 0 ) { // Group by range view.GroupDescriptions.Add( new PropertyGroupDescription("Age", new AgeToRangeConverter( ))); } else { view.GroupDescriptions.Clear( ); } } Figure 7-15. A custom value converter used for grouping 226 | Chapter 7: Binding to List Data way string comparison is done, you can pass in a member of the StringComparison enumeration. The PropertyGroupDescriptor can almost do it all. One thing it can’t do, at least by itself, is group at multiple levels. However, if you’d like to, you can add multiple group descriptors to the view’s GroupDescriptions list, as shown in Example 7-27. Grouping will be done in the order that the groups are described, indenting as appro- priate, as shown in Figure 7-16. Groups are pretty darn handy. They’re even handier if you’d like to combine them with sorting in your XAML. Example 7-27. Multiple groups void groupButton_Click(object sender, RoutedEventArgs e) { ICollectionView view = GetFamilyView( ); if( view.GroupDescriptions.Count == 0 ) { // Group by range, then age view.GroupDescriptions.Add( new PropertyGroupDescription("Age", new AgeToRangeConverter( ))); view.GroupDescriptions.Add( new PropertyGroupDescription("Age")); } else { view.GroupDescriptions.Clear( ); } } Figure 7-16. Grouping by more than one criterion Binding to List Data | 227 Declarative Sorting and Grouping Setting up sorting and grouping characteristics in code is handy if you want to flip the characteristics programmatically, as we’ve been doing. However, if you’ve got a predetermined set of data massaging you’d like to do, a CollectionViewSource is a handy place to keep those settings (see Example 7-28). Example 7-28. Declarative sorting and grouping 228 | Chapter 7: Binding to List Data In Example 7-28, we bring in the System.ComponentModel and System.Windows.Data namespaces first so that we can create SortDescription and PropertyGroupDescription objects. Then we create a CollectionViewSource object, which sorts and groups our data (provided via the Source property) and exposes an ICollectionView implementation. Inside the CollectionViewSource, we set up the sorting and grouping policies we’ve been setting up programmatically. Notice the use of multiple group descriptors, including one that brings in a custom value converter, just like our most advanced grouping code sample. Finally, we bind the listbox to the CollectionViewSource, so it can get the sorted and grouped data, as shown in Figure 7-17. Unfortunately, this technique isn’t quite as robust as the code-based technique; it doesn’t allow custom sorting code, nor does it allow filtering of any kind. However, it lets us go quite a way without imperative code (excluding the custom value con- verter, of course). Data Source Providers So far, we’ve been dealing with simple, hardcoded objects. However, objects can come from long operations for which we’d prefer not to wait, like over a network connection or as translated from XML or relational data. For these cases, we’d really like a layer of indirection for pulling objects from other sources and even pushing that work off to a worker thread if said retrieval is a ponderous operation. For this indirection, we turn to data source providers, whose job is (as the name suggests) to provide data sources for use in binding scenarios. Figure 7-17. Declarative sorting and grouping in action Data Source Providers | 229 Object Data Provider WPF ships with two data source providers, both derived from the DataSourceProvider base class: ObjectDataProvider and XmlDataProvider. Data source providers create a layer of indirection for any kind of operation that produces objects against which to data-bind. For example, if we wanted to load a set of Person objects over the Web, we could encapsulate that logic into a bit of code, as shown in Example 7-29. In Example 7-29, the RemotePeopleLoader class exposes a method (LoadPeople) that will load people however it feels and return that data for binding. To configure the object data provider to create the RemotePeopleLoader and call the LoadPeople method is a matter of a little XAML (see Example 7-30). Here we’re creating an ObjectDataProvider as a named resource so that we can use it as the data context for the grid, enabling binding at the listbox, text boxes, and so on. The ObjectType property is the type of the class to create, but you can use a pre- created object via the ObjectInstance property as well (e.g., if another resource was an object that could load data for you). The MethodName property is the name of the method to call to retrieve the data. Example 7-29. A type to be used by ObjectDataProvider ... public class Person : INotifyPropertyChanged {...} public class People : ObservableCollection {} public class RemotePeopleLoader { // ObjectDataProvider will expose results for binding public People LoadPeople() { // Load people from afar People people = new People( ); ... return people; } } ... Example 7-30. Using the ObjectDataProvider ... ... 230 | Chapter 7: Binding to List Data With an object data provider acting as an intermediary between the data and the bindings, we need to update our code to retrieve the People collection from the ObjectDataProvider resource, as shown in Example 7-31. Because the Family resource is now an ObjectDataProvider, itself derived from DataSourceProvider, in Example 7-31 when we need the People collection, we’re casting to DataSourceProvider on the Family resource and pulling the collection out of the Data property. Even though the object data provider exposes its data from the Data property, this does not mean you should bind to the Data property. If you notice from Example 7-30, we’re still binding the listbox as before: This works because WPF has built-in knowledge of DataSourceProvider, so there’s no need for you to do the indirection yourself. Example 7-31. Accessing the data held by an object data provider public partial class Window1 : Window { ... ICollectionView GetFamilyView( ) { DataSourceProvider provider = (DataSourceProvider)this.FindResource("Family"); People people = (People)provider.Data; return CollectionViewSource.GetDefaultView(people); } void birthdayButton_Click(object sender, RoutedEventArgs e) { ICollectionView view = GetFamilyView( ); Person person = (Person)view.CurrentItem; ++person.Age; MessageBox.Show(...); } void addButton_Click(object sender, RoutedEventArgs e) { DataSourceProvider provider = (DataSourceProvider)this.FindResource("Family"); People people = (People)provider.Data; people.Add(new Person("Chris", 37)); } ... } Data Source Providers | 231 Asynchronous data retrieval In Example 7-30, the object data provider was retrieving the data from the remote people loader synchronously, which means that if it took a long time, the UI thread would block. Instead, we can use the IsAsynchronous property to get the most inter- esting piece of functionality that the object data provider gives us and that we lack when we declare objects directly in XAML: When the IsAsynchronous property is set to True (the default is False), the task of retrieving the data is handled on a worker thread, letting the user continue to inter- act with the UI in the meantime and performing the binding on the UI thread only when the data has been retrieved. This is not the same as binding to the data as it’s retrieved (e.g., from a stream over the network), but it’s better than blocking the UI thread while a long retrieval happens. Passing parameters The object data provider also provides the MethodParameters property, which is a col- lection of objects to be passed to the method that retrieves the data. For example, if we wanted to pass in a set of URLs from which to try to retrieve the data, we could use the MethodParameters property as we do in Example 7-32. Example 7-32. Passing method parameters via ObjectDataProvider http://sellsbrothers.com/boys.dat http://sellssisters.com/girls.dat ... ... 232 | Chapter 7: Binding to List Data In Example 7-32, we’ve added a list of two URLs, which will be translated into a call to the LoadPeople method that takes two strings (see Example 7-33). Using the object data provider and any method on any object that returns data, you can retrieve data asynchronously and bind to it when it’s available. Although it’s pos- sible to create your own custom data source provider (just derive from DataSourceProvider and have a party), the flexibility of the object data provider means that you almost certainly won’t need to. The object data provider allows for another way to get the data as well as from a named method. If you don’t provide a MethodName, the object data provider will assume that the data is retrieved in the constructor (either the default or as described by the ConstructorParameters list, structured just like the MethodParameters list) and that the object itself is the data. The use of the constructor and optional constructor parameters is handy if you’re binding to one or more collections exposed from properties on the constructed object. For example: ... ... ... ... In this case, you might want to use the IsAsync property on binding to the lower-level data instead of the IsAsynchronous property on the top- level data provider, as now the former is likely to take longer. Example 7-33. Accepting arguments passed by ObjectDataProvider namespace PersonBinding { public class RemotePeopleLoader : People { public People LoadPeople(string url1, string url2) { // Load People from afar using two URLs ... } } Data Source Providers | 233 Binding to Relational Data Although UI designer support is still being developed to help bring relational data into your WPF pages specifically, the tools we’ve already got can be pressed into ser- vice for WPF work without issue. For example, assume a table like the one in Figure 7-18 defined in an Access database (family.mdb). Although we could write the ADO.NET code to bring this table into our project, we don’t have to. Instead, we can bring in the data using the typed dataset designer, which has been in Visual Studio since .NET 1.0. Bringing a new typed data set into your project is as simple as right-clicking on your project, choosing Add ➝ New Item ➝ Dataset, choosing a name, and clicking Add. This brings up the typed dataset designer, onto which you can drag any number of tables, setting up relationships and specifying the way you’d like the data to be projected into your project. A ready source of data for the data set design is the Server Explorer, which you can use to connect to various databases. To connect to the Access database, family.mdb, I right-clicked on Data Connections and chose Add Connection, configuring things properly for Access. I then drilled in to the People table and dragged it onto the designer surface, as shown in Figure 7-19. Figure 7-18. A Person table in Access (family.mdb) Figure 7-19. Creating a typed data set in a WPF project works just fine 234 | Chapter 7: Binding to List Data All of these dragging and dropping shenanigans produced for me three interesting classes: PeopleRow, PeopleDataTable, and PeopleTableAdapter, summarized in Example 7-34 from the generated Family.Designer.cs file. The PeopleRow class is a typed wrapper around the DataRow class built into ADO.NET. It’s the thing that maps between the underlying database types and the CLR types. When you bind to relational data in WPF, you’ll be binding to a DataTable full of these DataRow-derived objects. Actually, just plain DataRow objects work, too—you don’t have to use the typed dataset designer to make this work. However, if you do, you also get the benefit of the generated table adapters, like our PeopleDataTable, which knows the shortest way to create and find PeopleRow objects, and the PeopleTableAdapter, which knows how to read and write data to and from Access (in our case), to get the data and track updates for pushing back to the database. The one other thing we get is the connection string plopped into the app.config so that it can be maintained separately from the code, as you can see in Example 7-35. Example 7-34. The interesting class the typed dataset designer generates namespace AdoBinding { ... public partial class Family : System.Data.DataSet { ... public partial class PeopleRow : System.Data.DataRow { ... public int ID { get {...} set {...} } } public string Name { get {...} set {...} } } public int Age { get {...} set {...} } } ... } public partial class PeopleDataTable : System.Data.DataTable, System.Collections.IEnumerable { ... public PeopleRow AddPeopleRow(string Name, int Age) {...} public PeopleRow FindByID(int ID) {...} public void RemovePeopleRow(PeopleRow row) {...} ... } } namespace FamilyTableAdapters { ... public partial class PeopleTableAdapter : System.ComponentModel.Component { ... public virtual Family.PeopleDataTable GetData() {...} ... } } } Data Source Providers | 235 With these two wrappers in place, and the connection string all set up for us in the app’s .config file, all we really have to do is create an instance of the PeopleTableAdapter and call GetData, binding to the results. We could do this in the main window’s con- structor if we wanted to, as shown in Example 7-36. In Example 7-36, the GetData call is synchronous, which is fine for our simple sam- ple. However, because in a real app we’re often accessing data that is located over a network connection, synchronously retrieving the data and blocking the UI thread while we wait isn’t such a good idea. This is an excellent use of the asynchronous support we’ve got in the object data provider (see Example 7-37). Example 7-35. The connection string we get when we add a new data connection Example 7-36. Using the classes generated by the dataset designer public Window1( ) { InitializeComponent( ); // Get the data for binding synchronously DataContext = (new FamilyTableAdapters.PeopleTableAdapter()).GetData( ); ... } Example 7-37. Binding to relational data declaratively ... 236 | Chapter 7: Binding to List Data At the top of Example 7-37, we’re doing just what we did in code—that is, creating an instance of the PeopleTableAdapter type, calling GetData, and binding to the results. The difference is that we’re doing it declaratively, which makes it very easy to bind asynchronously—all we have to do is set the IsAsynchronous property to True and the data retrieval happens on a worker thread, keeping the UI from freezing. Another thing to notice is that the bindings are all the same as before, although in this case, we’re using the names of the columns as properties and trusting ADO.NET and WPF to negotiate properties dynamically at runtime via the ICustomTypeDescriptor interface.* Finally, notice that our use of the age to foreground brush value converter remains the same. Example 7-37 uses a template created specifically for use by the ListBox object’s ItemTemplate property instead of a typed data tem- plate to automatically share across content controls. This is because we’re no longer dealing with objects of the custom Person class at the top level of a namespace, but objects of type DataRowView. Because we’ve got the ADO.NET DataRowView type and the typed dataset designer- generated PeopleDataTable and PeopleDataRow types instead of our custom Person and People types, implementing our data management code is a little different, as you can see in Example 7-38. (age: ) ... * The ICustomTypeDescriptor interface has been with us since .NET 1.0 for data bound objects to expose prop- erties not known until runtime (e.g., the dynamicresults of an SQL query). In the caseof ADO.NET, even though we used the typed dataset designer to get typed properties, WPF will still use the DataRowView class’s implementation of ICustomTypeDescriptor, which is why typed and untyped data sets work equally well. Example 7-37. Binding to relational data declaratively (continued) Data Source Providers | 237 Example 7-38. Accessing the data held by ADO.NET // Window1.xaml.cs ... using System.Data; using System.Data.OleDb; public partial class Window1 : Window { public Window1( ) { InitializeComponent( ); this.birthdayButton.Click += birthdayButton_Click; this.backButton.Click += backButton_Click; this.forwardButton.Click += forwardButton_Click; this.addButton.Click += addButton_Click; this.sortButton.Click += sortButton_Click; this.filterButton.Click += filterButton_Click; this.groupButton.Click += groupButton_Click; } ICollectionView GetFamilyView( ) { DataSourceProvider provider = (DataSourceProvider)this.FindResource("Family"); return CollectionViewSource.GetDefaultView(provider.Data); } void birthdayButton_Click(object sender, RoutedEventArgs e) { ICollectionView view = GetFamilyView( ); // Each item is a DataRowView, which we can use to access // the typed PersonRow AdoBinding.Family.PeopleRow person = (AdoBinding.Family.PeopleRow)((DataRowView)view.CurrentItem).Row; ++person.Age; MessageBox.Show( string.Format( "Happy Birthday, {0}, age {1}!", person.Name, person.Age), "Birthday"); } void backButton_Click(object sender, RoutedEventArgs e) { ICollectionView view = GetFamilyView( ); view.MoveCurrentToPrevious( ); if( view.IsCurrentBeforeFirst ) { view.MoveCurrentToFirst( ); } } void forwardButton_Click(object sender, RoutedEventArgs e) { ICollectionView view = GetFamilyView( ); 238 | Chapter 7: Binding to List Data view.MoveCurrentToNext( ); if( view.IsCurrentAfterLast ) { view.MoveCurrentToLast( ); } } void addButton_Click(object sender, RoutedEventArgs e) { // Creating a new PeopleRow DataSourceProvider provider = (DataSourceProvider)this.FindResource("Family"); AdoBinding.Family.PeopleDataTable table = (AdoBinding.Family.PeopleDataTable)provider.Data; table.AddPeopleRow("Chris", 37); } void sortButton_Click(object sender, RoutedEventArgs e) { ICollectionView view = GetFamilyView( ); if( view.SortDescriptions.Count == 0 ) { view.SortDescriptions.Add( new SortDescription("Name", ListSortDirection.Ascending)); view.SortDescriptions.Add( new SortDescription("Age", ListSortDirection.Descending)); } else { view.SortDescriptions.Clear( ); } } void filterButton_Click(object sender, RoutedEventArgs e) { // Can't set the Filter property, but can set the // CustomFilter on a BindingListCollectionView BindingListCollectionView view = (BindingListCollectionView)GetFamilyView( ); if( string.IsNullOrEmpty(view.CustomFilter) ) { view.CustomFilter = "Age > 25"; } else { view.CustomFilter = null; } } void groupButton_Click(object sender, RoutedEventArgs e) { ICollectionView view = GetFamilyView( ); if( view.GroupDescriptions.Count == 0 ) { // Group by age view.GroupDescriptions.Add(new PropertyGroupDescription("Age")); } else { view.GroupDescriptions.Clear( ); } } } Example 7-38. Accessing the data held by ADO.NET (continued) Data Source Providers | 239 In Example 7-38, you’ll notice that manipulating and displaying a person is different because we’re dealing with a DataRowView object’s Row property to get the typed PeopleRow we want. Also, adding a new person is different because we’re dealing with a PeopleDataTable. Finally, filtering is different because the BindingListCollectionView doesn’t support the Filter property (setting it causes an exception at runtime). How- ever, we set the CustomFilter string on the BindingListCollectionView using the ADO. NET filter syntax. Everything else, though—including accessing the collection view, navigating the rows, and even sorting and grouping—is the same, as shown in Figure 7-20. So, although there was no relational data-specific data provider, none is needed—the object data provider works just fine for data binding to relational data in WPF. XML Data Source Provider In addition to object and relational data, WPF also supports binding to XML data. For instance, Example 7-39 shows some family data represented in XML. With this file available in the same folder as the executing application, we can bind to it using the XmlDataProvider, as shown in Example 7-40. Figure 7-20. ADO.NET data binding in action Example 7-39. A random family rendered in XML 240 | Chapter 7: Binding to List Data The first thing I want to point out in Example 7-40 is the use of the XmlDataProvider with a relative URL that points to the family.xml file. The first thing you’ll probably notice, though, is the large amount of XAML to deal with namespaces. Looking back at the XML file (Example 7-39), you’ll notice that no prefix was used, only a default namespace of http://sellsbrothers.com. Using namespace prefixes in the XAML makes it possible to construct the XPath statement to find the set of Person elements in our sam- ple XML. Finally, notice the use of the XPath property in the Binding objects instead of the Path property, and the @ symbol to designate binding to an XML attribute.* Example 7-40. An XmlDataProvider in action ... ... * An explanation of the XPath syntax is beyond the scope of this book, but for a good reference, I’d start with Essential XML Quick Reference, by Aaron Skonnard and Martin Gudgin (Addison-Wesley Professional). Data Source Providers | 241 XML data islands If you happen to know your data at compile time, the XML data provider also sup- ports XML data islands, as shown in Example 7-41. In Example 7-41, we’ve copied the contents of family.xml under the XmlDataProvider element and wrapped it in an XData element to designate it as separate from the rest of how XAML is parsed (Appendix A is a good place to read up on that topic). We’ve also dropped the Source attribute (because the data is embedded), but left the XPath statement as it was. And as you might expect, now that we’re using XML instead of object data, some of the operations in our sample application need to be changed (see Example 7-42). Example 7-41. An XML data island in XAML Example 7-42. Managing XML bound data // Window1.xaml.cs ... using System.Xml; public partial class Window1 : Window { public Window1( ) { InitializeComponent( ); this.birthdayButton.Click += birthdayButton_Click; this.backButton.Click += backButton_Click; this.forwardButton.Click += forwardButton_Click; this.addButton.Click += addButton_Click; this.sortButton.Click += sortButton_Click; this.filterButton.Click += filterButton_Click; this.groupButton.Click += groupButton_Click; } 242 | Chapter 7: Binding to List Data ICollectionView GetFamilyView( ) { DataSourceProvider provider = (DataSourceProvider)this.FindResource("Family"); return CollectionViewSource.GetDefaultView(provider.Data); } void birthdayButton_Click(object sender, RoutedEventArgs e) { ICollectionView view = GetFamilyView( ); // Each "person" is an XmlElement and attribute // values come from a string-based indexer XmlElement person = (XmlElement)view.CurrentItem; person.SetAttribute("Age", (int.Parse(person.Attributes["Age"].Value) + 1).ToString( )); MessageBox.Show( string.Format( "Happy Birthday, {0}, age {1}!", person.Attributes["Name"].Value, person.Attributes["Age"].Value), "Birthday"); } void backButton_Click(object sender, RoutedEventArgs e) { ICollectionView view = GetFamilyView( ); view.MoveCurrentToPrevious( ); if( view.IsCurrentBeforeFirst ) { view.MoveCurrentToFirst( ); } } void forwardButton_Click(object sender, RoutedEventArgs e) { ICollectionView view = GetFamilyView( ); view.MoveCurrentToNext( ); if( view.IsCurrentAfterLast ) { view.MoveCurrentToLast( ); } } void addButton_Click(object sender, RoutedEventArgs e) { // Creating a new XmlElement XmlDataProvider provider = (XmlDataProvider)this.FindResource("Family"); XmlElement person = provider.Document.CreateElement("Person", "http://sellsbrothers.com"); person.SetAttribute("Name", "Chris"); person.SetAttribute("Age", "37"); provider.Document.ChildNodes[0].AppendChild(person); } Example 7-42. Managing XML bound data (continued) Data Source Providers | 243 Whereas in the ADO.NET example we used PeopleDataTable, PeopleDataRow, and DataRowView, in the XML example we use XmlDocument and XmlElement. For updating and accessing values, Example 7-42 uses the XmlElement SetAttribute method to change a value and the Attributes collection to get one. When adding a new person, we get the XmlDocument from the XmlDataProvider, ask it to create a new XmlElement, set the attributes, and add it to the child node collection of the document. When filtering, we simply cast to an XmlElement to access the attributes we need to make filter- ing decisions. Finally, when sorting or grouping, the descriptions include paths as XPath expressions (e.g., @Age). The results look just like Figure 7-20. void sortButton_Click(object sender, RoutedEventArgs e) { ICollectionView view = GetFamilyView( ); if( view.SortDescriptions.Count == 0 ) { view.SortDescriptions.Add( new SortDescription("@Name", ListSortDirection.Ascending)); view.SortDescriptions.Add( new SortDescription("@Age", ListSortDirection.Descending)); } else { view.SortDescriptions.Clear( ); } } void filterButton_Click(object sender, RoutedEventArgs e) { ICollectionView view = GetFamilyView( ); if( view.Filter == null ) { view.Filter = delegate(object item) { return int.Parse(((XmlElement)item).Attributes["Age"].Value) > 25; }; } else { view.Filter = null; } } void groupButton_Click(object sender, RoutedEventArgs e) { ICollectionView view = GetFamilyView( ); if( view.GroupDescriptions.Count == 0 ) { // Group by age view.GroupDescriptions.Add(new PropertyGroupDescription("@Age")); } else { view.GroupDescriptions.Clear( ); } } } Example 7-42. Managing XML bound data (continued) 244 | Chapter 7: Binding to List Data XML binding without the data source provider If you’ve already got a source of XML data that isn’t readily available for use by the XML data source provider,* you can programmatically bind to it instead, as shown in Example 7-43. * For example, if you need to retrieve XML data via an HTTP POST, you can’t use the XML data source pro- vider, as it can only use HTTP GET. Example 7-43. XML binding without the data source provider ... // Window1.xaml.cs ... public partial class Window1 : Window { // the family XML document XmlDocument doc; public Window1( ) { ... LoadFamilyXml(); } void LoadFamilyXml() { // Load the XML using an XmlDocument doc = new XmlDocument( ); doc.Load("family.xml"); // Make the namespace prefix mappings available for use in binding XmlNamespaceManager manager = new XmlNamespaceManager(doc.NameTable); manager.AddNamespace("sb", "http://sellsbrothers.com"); Binding.SetXmlNamespaceManager(grid, manager); // Make the XML available for data binding. We use a binding here // because it will detect when the source document changes so it can // refresh the set of nodes returned by the XPath query Binding b = new Binding( ); b.XPath = "/sb:Family/sb:Person"; b.Source = doc; grid.SetBinding(Grid.DataContextProperty, b); } Master-Detail Binding | 245 In Example 7-43, we’re loading the XML manually from a file, but you can get access to the XML in whatever way is convenient, as long as you have an XmlNode or XmlNodeList to which to bind. Here we’re creating the XmlDocument as a member variable so that we can use it again to create and add a new XmlElement in the addButton_Click event handler. Notice also that we’re populating an XmlNamespaceManager and binding it to the grid so that binding knows how to trans- late XPath strings that use namespace prefixes. And finally, instead of setting the XML data directly as the grid’s DataContext, we’re actually binding it, along with the XPath to filter the set of nodes available in the XML data. The binding is there so that when the underlying XML data changes, resulting in a new set of nodes returned from the XPath expression, the grid’s data context is updated appropriately. Also, as this data context changes, the view may change, so we’re using the DataContext property of the grid to get the view in GetFamilyView each time we need it. The rest of the XML-related code in this sample does not have to change, as we’ve just done manually what the XML data source provider was doing for us (although we did leave out support for asynchronous access to the data, if it happens to be far away). Master-Detail Binding We’ve seen binding to a single object. We’ve seen binding to a single list of objects. Another very popular thing to do is to bind to more than one list, especially related lists. For example, if you’re showing your users a list of customers and then, when they select one, you’d like to show that customer’s related orders, you’ll want master-detail binding. ICollectionView GetFamilyView() { // The default view comes directly from the data return CollectionViewSource.GetDefaultView(grid.DataContext); } ... void addButton_Click(object sender, RoutedEventArgs e) { // Creating a new XmlElement XmlElement person = doc.CreateElement("Person", "http://sellsbrothers.com"); person.SetAttribute("Name", "Chris"); person.SetAttribute("Age", "37"); doc.DocumentElement.AppendChild(person); } ... } Example 7-43. XML binding without the data source provider (continued) 246 | Chapter 7: Binding to List Data Master-detail binding is a form of filtering, where the selection in the master list (e.g., customer 452) sets the filtering parameters for the associated detail data (e.g., orders for customer 452). In our discussion thus far, we don’t have customers and orders, but we do have fami- lies and people, which we could further formalize as shown in Example 7-44. In Example 7-44, we’ve got our familiar Person class with Name and Age properties, collected into a familiar People collection. Further, we have a Family class with a FamilyName property and a Members property of type People. Finally, we have a Families collection, which collects Family objects. In other words, families have members, which consist of people with names and ages. You could imagine instances of Families, Family, People, and Person that looked like Figure 7-21. Example 7-44. Master-detail data for binding public class Person { string name; public string Name { get { return name; } set { name = value; } } int age; public int Age { get { return age; } set { age = value; } } } public class People : ObservableCollection {} public class Family { string familyName; public string FamilyName { get { return familyName; } set { familyName = value; } } People members; public People Members { get { return members; } set { members = value; } } } public class Families : ObservableCollection {} Master-Detail Binding | 247 In Figure 7-21, the Families collection forms the master data, holding instances of the Family class, each of which holds a Members property of type People, which holds the detail Person data. You could populate instances of these data structures as shown in Example 7-45. Figure 7-21. Example master-detail data Example 7-45. Declaring example master-detail data Family .Name = "Stooges" .Members Family .Name = "Addams" .Members Families (Master) Person .Name = "Larry" .Age = 21 Person .Name = "Moe" .Age = 23 People (Details) Person .Name = "Curly" .Age = 22 Person .Name = "Gomez" .Age = 135 Person .Name = "Morticia" .Age = 121 People (Details) Person .Name = "Fester" .Age = 137 248 | Chapter 7: Binding to List Data Binding to this data at the top level (i.e., to show the family names) could look like Example 7-46. In Example 7-46, we’re setting two things in the Families column (column 0). The first is the header, which is set to the constant string Families. The second forms the body, which is a list of Family objects in the Families collection, showing each family’s FamilyName property, as shown in Figure 7-22. Figure 7-22 isn’t master-detail yet, because selecting a master family doesn’t show its associated details. To do that, we need to bind to the next level, as shown in Example 7-47. ... Example 7-46. Binding to master Family data ... ... Families: Example 7-45. Declaring example master-detail data (continued) Master-Detail Binding | 249 In the Members column (column 1), we’re also setting a header and body, but this time the header is bound to the FamilyName of the currently selected Family object. Also, recall that in the Families column, our listbox’s items source was bound to the entire collection via a Binding statement without a Path. In the details case, however, we want to tell the data binding engine that we’d like to bind to the Members prop- erty of the currently selected Family object, which is itself a collection of Person objects. Figure 7-23 shows master-detail binding in action. But wait; there’s more! Master-detail binding doesn’t stop at just two levels, oh no. You can go as deep as you like, with each detail binding becoming the master bind- ing for the next level. To see this in action, let’s add one more level of detail to our data classes (see Example 7-48). Figure 7-22. Showing family data Example 7-47. Binding to detail Person data ... ... 250 | Chapter 7: Binding to List Data Now, not only do families have family names and members that consist of people with names and ages, but each person also has a set of traits, each with its own description. Expanding our XAML a bit to include traits would look like Example 7-49. Figure 7-23. Showing master Family and detail Person data Example 7-48. Adding a third level of detail public class Person { string name; public string Name { get { return name; } set { name = value; } } int age; public int Age { get { return age; } set { age = value; } } Traits traits; public Traits Traits { get { return traits; } set { traits = value; } } } public class Traits : ObservableCollection {} public class Trait { string description; public string Description { get { return description; } set { description = value; } } } Example 7-49. Declaring a third level of detail Master-Detail Binding | 251 With a third level of detail, we bind as shown in Example 7-50. In the case of the Families column header, recall that we had no binding at all; the text was hardcoded: Families: ... ... ... ... Example 7-50. Binding to a third level of detail data ... ... ... Example 7-49. Declaring a third level of detail (continued) 252 | Chapter 7: Binding to List Data In the case of the Members column header, we bound to the FamilyName of the cur- rently selected Family object like so: Logically, you could think of that as expanding to the following: where family is the currently selected Family object. Taking this one level deeper, in the case of the traits column header, we’re binding to the Name property of the currently selected Person from the Members property of the currently selected Family, which binds like this: Again, logically you could think of it expanding like this: where family is the currently selected Family object and person is the currently selected Person object. The / in the binding statement acts as the separator between objects, with the object at each level assumed to be “currently selected.” The binding for the listbox’s items source works the same way, except we want the Traits collection from the currently selected Person, not the Name. Our trilevel master- detail example looks like Figure 7-24. Hierarchical Binding Master-detail binding is one step away from true hierarchical binding in that it generally involves a known set of levels. For example, when we wanted to go from two levels to three levels, we added another column to the table and manually set up the relationship at the new level. On the other hand, hierarchical binding (sometimes called tree binding) generally involves some number of levels that aren’t known until runtime and a control that can expand itself as appropriate, like a menu or a tree. WPF has built-in support for hierarchical binding using a special kind of data template that knows both how to dis- play the current level of data and where to go for the next level. It’s a bit involved, though, so let’s go back to first principles with our family data (see Example 7-51). Figure 7-24. Showing master-detail–more detail data Hierarchical Binding | 253 In Example 7-51, we’re binding a TreeView control’s root item to the top level of the families data, labeling the root “Families,” as shown in Figure 7-25. Because the Families collection contains two Family objects, but we haven’t pro- vided a template, WPF shows them as their type. If we want to show something more meaningful, we already know to provide a data template (see Example 7-52). The result is that we see the family name for each family in the collection, as shown in Figure 7-26. Example 7-51. The beginnings of hierarchical data binding ... Figure 7-25. The beginnings of hierarchical data binding Example 7-52. Slightly better hierarchical data binding ... 254 | Chapter 7: Binding to List Data Figure 7-26 looks better, but now we’ve dead-ended our tree because the TreeViewItem element doesn’t know where to get the next level of data. To provide this data, we have the hierarchical data template, shown in Example 7-53. In Example 7-53, the HierarchicalDataTemplate element is exactly the same as the normal DataTemplate element, except that it provides the ItemsSource property so that the tree can keep digging into the data, as shown in Figure 7-27. Figure 7-26. Slightly better hierarchical data binding Example 7-53. The next level of hierarchical data binding ... Figure 7-27. The next level of hierarchical data binding Hierarchical Binding | 255 Once again, the default behavior is to show the type name. We need to provide one last template to show something more useful for the Person objects. Because these are the leaves on our tree, we can use an ordinary data template, as shown in Example 7-54. Notice in Example 7-54 that we have two hierarchical data templates (one for Family, which contain Person objects, and one for Person, which contains Trait objects) and one normal data template (for the Trait object, which doesn’t contain anything else). With these templates in place, we get a tree that looks like Figure 7-28. In you take another look at Example 7-54, you’ll notice that we’re not describing the overall structure of the tree, but only how to get from any one object to its children. This means that wherever an object of a type that has a hierarchical data template appears in the tree, we can get to its children. For example, if you had Folder and File, where Folder had a collection that contained both Files and Folders, Folders would open to arbitrary levels in the tree given a single hierarchical data template that told WPF how to get to those children. This makes hierarchical data binding much more flexible than master-detail binding. Example 7-54. Plumbing all of the hierarchical nodes ... 256 | Chapter 7: Binding to List Data Where Are We? Whereas the preceding chapter dealt with the fundamentals of data binding, in this chapter we discussed those topics necessary to make the most of binding to lists of data, including list data sources in object, relational, and XML data formats; manag- ing the current item; value conversion; sorting; filtering; grouping; data templates; and even master-detail and hierarchical relationships. It may seem hard to believe, but there are things that WPF’s data binding engine supports that we haven’t dis- cussed (some of which we’ll get to in the next chapter, but some of which are beyond the scope of this book*). The thorough support for data binding at every level of WPF makes it a first-class feature in a way that data binding has never been before. You’ll find that it perme- ates pretty much every aspect of your WPF programming, including styles and con- trol templates, which are the topics of the next two chapters. Figure 7-28. Hierarchical data binding in action * PriorityBinding and MultiBinding are the two topics that leap to mind as being uncovered in this book; for details, refer to the Windows Platform SDK documentation at http://msdn2.microsoft.com/en-us/library/ default.aspx (http://tinysells.com/68). 257 Chapter 8 CHAPTER 8 Styles8 In a word-processing document, a style is a set of properties to be applied to ranges of content (e.g., text, images, etc.). For example, the name of the style I’m using now is called Normal,Body,b and for this document in prepublication, that means a font family of Times, a size of 10, and full justification. Later in the document, I’ll be using a style called Code,x,s, which will use a font family of Courier New, a size of 9, and left justification. Styles are applied to content to produce a certain look when the content is rendered. In WPF, a style is also a set of properties applied to content used for visual render- ing, like setting the font weight of a Button control. In addition to the features in word-processing styles, WPF styles have specific features for building applications, including the ability to apply different visual effects based on user events. All of these features come without the need to build a custom control (although that’s still a use- ful thing to be able to do, as discussed in Chapter 18). Without Styles As an example of how styles can make themselves useful in WPF, let’s look at a sim- ple implementation of tic-tac-toe (see Example 8-1). Example 8-1. A simple tic-tac-toe layout 258 | Chapter 8: Styles This grid layout arranges a set of nine buttons in a 3 × 3 grid of tic-tac-toe cells, using the margins on the button for the tic-tac-toe crosshatch. A simple implementation of the game logic in the XAML code-behind file looks like Example 8-2. * However, an inline style is useful if you want to add property and data triggers to an individual element. We discuss triggers later in this chapter. 262 | Chapter 8: Styles Named Styles By hoisting the same inline style into a resource (as introduced in Chapter 1), we can award it a name and use it by name in our button instances, as shown in Example 8-5. In Example 8-5, we’ve used the class name as a prefix on our properties so that the style knows what dependency property we’re talking about. We used Control as the prefix instead of Button to allow the style to be used more broadly, as we’ll soon see. The Target Type Attribute As a convenience, if all of the properties can be set on a shared base class, like Control in our example, we can promote the class prefix into the TargetType attribute and remove it from the name of the property (see Example 8-6). When providing a TargetType attribute, you can only set properties available on that type. If you’d like to expand to a greater set of properties down the inheritance tree, you can do so by using a more derived type (see Example 8-7). Example 8-5. Setting a named style ... Figure 9-2. Replacing the control template with something less visual than we’d like… 286 | Chapter 9: Control Templates Now we’re getting somewhere, as Figure 9-3 shows. Notice how square the corners are now? Also, if you click, you won’t get the depres- sion that normally happens with a button (and I don’t mean “a sad feeling”). We have taken complete control over the look of the button or, to paraphrase some ancient pop culture, “all your button are belong to us...” Control Templates and Styles Now that we’re making some progress on the control template, let’s replicate it to the other buttons. We could do that by setting each button’s Template property by hand, either to a copy of the control template or with a reference to a ControlTemplate element that’s been created in a Resource element. However, it’s often most convenient to bundle the control template with the button’s style, as Example 9-2 illustrates. Figure 9-3. Replacing the button’s control template with an orange rectangle (Color Plate 13) Example 9-2. Putting a control template into a style ... ... Beyond Styles | 287 As Example 9-2 shows, the Template property is the same as any other and can be set with a style. Figure 9-4 shows the results. Here we have the classic crosshatch we’ve been aiming for, but the orange is kind of jarring. What if the Button object’s Background property was set to something more reasonable (maybe white?) and we’re ignoring it, favoring colors from scary holidays not known for their design sense? We can solve this problem with template bindings. Template Binding If we wanted white buttons, we could hardcode the rectangle’s fill to be white, but what happens when a style wants to change it (maybe somebody really wants an orange tic-tac-toe board)? Instead of hardcoding the fill of the rectangle, we can reach out of the template into the properties of the control by using template bind- ing, as shown in Example 9-3. Example 10-6. A simple custom settings dialog (continued) Dialogs | 327 location and use a horizontal stack panel to keep the OK and Cancel buttons clus- tered together along the bottom right of the dialog. We’re also using labels, access keys, and a resize grip, but those are just good practices and not dialog-specific. With the grid lines turned on, our settings dialog looks like Figure 10-4. To take advantage of our custom dialog, we have but to create an instance and show it modally, as in Example 10-7. Although we’ve gotten a pretty good dialog using standard window techniques, dia- logs need more to fit into a Windows world: • The initial focus is set on the correct element. • The dialog doesn’t show in the taskbar. • Data is passed in and out of the dialog. • The OK button is shown as the default button (and is activated when the Enter key is pressed). • Cancel is activated when the Esc key is pressed. • Data is validated before “OK” is really “OK.” Figure 10-4. Using standard WPF techniques on a dialog Example 10-7. Showing a custom dialog void settingsButton_Click(object sender, RoutedEventArgs e) { // Create dialog and show it modally, centered on the owner SettingsDialog dlg = new SettingsDialog(); dlg.Owner = this; if (dlg.ShowDialog( ) == true) { // Do something with the dialog properties ... } } 328 | Chapter 10: Windows and Dialogs Dialog look and feel Addressing issues on this list, we realize our dialog doesn’t really feel like a dialog (e.g., the initial focus isn’t set). Plus, like any window, the dialog shows as another window in the taskbar (which it shouldn’t). We can fix both of these by setting two properties on the window, as shown in Example 10-8. In Example 10-8, the FocusedElement property allows us to bind to the element that we’d like to give the initial focus, and the ShowInTaskBar lets us keep the dialog out of the taskbar. Figure 10-5 shows the results. The only traditional dialog feature that WPF doesn’t support is the ? icon on the cap- tion bar. However, as alternatives you can handle F1 (as described in Chapter 4), add a Help button, or use tool tips (as we’ll see) as alternatives. Example 10-8. Setting dialog-related Window properties ... Figure 10-5. A good-looking dialog in WPF Dialogs | 329 Dialog data exchange Now that we’ve got our dialog looking like a dialog, we also want it to behave like one. A dialog’s behavior is governed by its lifetime, which looks roughly like the following: 1. Create and initialize the dialog with initial values. 2. Show the dialog, letting the user choose new, validated values. 3. Harvest the values for use in your application. Modal dialogs are generally provided to get data from a user so that some operation can be handled on his behalf. In the case of the sample settings dialog, I’m asking for a folder to store reports and specifying what color those reports should be. That information, like the information exposed from the standard dialogs, should be exposed as properties that I can set with initial values before showing the dialog and get after the user has changed them and clicked OK, as shown in Example 10-9. You’ll notice that we’re using a degree of good old-fashioned object-oriented encap- sulation here, passing in and harvesting the values using .NET properties, but hav- ing no say about how the dialog shows those values to the user or how they’re changed. All of this happens during the call to ShowDialog (which we’ll get to directly). If the user clicks the OK button (or equivalent), we trust the dialog to let us know by returning a result of True so that we know to make use of the approved new values. Example 10-9. Exchanging data with a modal dialog class Window1 : Window { ... Color reportColor; string reportFolder; void settingsButton_Click(object sender, RoutedEventArgs e) { // 1. Create and initialize the dialog with initial values SettingsDialog dlg = new SettingsDialog( ); dlg.Owner = this; dlg.ReportColor = reportColor; dlg.ReportFolder = reportFolder; // 2. Show the dialog, letting the user choose new, validated values if (dlg.ShowDialog( ) == true) { // 3. Harvest the values for use in your application reportColor = dlg.ReportColor; reportFolder = dlg.ReportFolder; // Do something with these values... } } } 330 | Chapter 10: Windows and Dialogs When you’re implementing a custom dialog, how you implement the dialog proper- ties is a matter of taste. Because I want to use data binding as part of the dialog implementation, I built a little class (Example 10-10) to hold the property data and fire change notifications. Example 10-10. Managing custom dialog data public partial class SettingsDialog : System.Windows.Window { // Data for the dialog that supports notification for data binding class DialogData : INotifyPropertyChanged { Color reportColor; public Color ReportColor { get { return reportColor; } set { reportColor = value; Notify("ReportColor"); } } string reportFolder; public string ReportFolder { get { return reportFolder; } set { reportFolder = value; Notify("ReportFolder"); } } // INotifyPropertyChanged Members public event PropertyChangedEventHandler PropertyChanged; void Notify(string prop) { if( PropertyChanged != null ) { PropertyChanged(this, new PropertyChangedEventArgs(prop)); } } } DialogData data = new DialogData(); public Color ReportColor { get { return data.ReportColor; } set { data.ReportColor = value; } } public string ReportFolder { get { return data.ReportFolder; } set { data.ReportFolder = value; } } public SettingsDialog( ) { InitializeComponent( ); // Allow binding to the data to keep UI bindings up-to-date DataContext = data; reportColorButton.Click += reportColorButton_Click; folderBrowseButton.Click += folderBrowseButton_Click; ... } Dialogs | 331 The DialogData class in Example 10-10 is private to the dialog class and only serves as storage for the data that allows binding, which we enable by setting the DataContext to an instance of the class in the dialog’s constructor. The dialog proper- ties expose the data with properties that merely redirect to the instance of the DialogData class. When the data changes during the operation of the dialog (like when the user browses to a new folder or changes the color in a subdialog), setting the ReportColor and ReportFolder properties triggers a change notification, updating the dialog UI, which you’ll recall is data bound as shown in Example 10-11. void reportColorButton_Click(object sender, RoutedEventArgs e) { // Set the ReportColor property, triggering a change notification // and updating the dialog UI ... } void folderBrowseButton_Click(object sender, RoutedEventArgs e) { // set the ReportFolder property, triggering a change notification // and updating the dialog UI ... } ... } Data Binding and Dialogs You’ll want to avoid binding directly to reference type objects passed into a dialog. The problem is that you could well be in a situation where the user makes changes to data and then clicks the Cancel button. In that case, WPF provides no facilities for rolling back changes made via data binding. That’s why our example settings dialog keeps its own copies of the color and folder information. Example 10-11. Binding property data in a custom dialog’s GUI ... ... ... ... Example 10-10. Managing custom dialog data (continued) 332 | Chapter 10: Windows and Dialogs In Example 10-11, the report folder text box is binding to the ReportFolder property of the DialogData object. The button’s background brush is binding to the brush (using the StaticResource markup extension described in Chapter 12) constructed via a binding to the ReportColor property (also of the DialogData object). Figure 10-6 shows our settings dialog in various stages of data change. Handling OK and Cancel Now that we know how to get the dialog running the way we want it to, we need to let the calling code know whether to process the data exposed after the dialog is closed. You can always close a dialog with the Window object’s Close method, shown in Example 10-12. By default, when calling the Close method, or if the user clicks the Close button or presses Alt-F4, the result from ShowDialog will be false, indicating “cancel.” If you’d like the return from ShowDialog to be true, to indicate “OK,” you need to set the dia- log’s DialogResult property, as shown in Example 10-13. Figure 10-6. The custom settings dialog in action (Color Plate 18) Example 10-12. Closing a dialog manually class SettingsDialog : Window { ... void cancelButton_Click(object sender, RoutedEventArgs e) { // The result from ShowDialog will be false Close(); } } Dialogs | 333 The DialogResult property is public, so it’s available to users of your custom dia- logs. The vast majority of the time, the dialog’s DialogResult property will be the same as the return from ShowDialog. To understand the corner case, let’s look at Example 10-14, which shows the definitions of ShowDialog and DialogResult. If you’re not familiar with the ? syntax, it designates the Boolean type of ShowDialog and DialogResult to be nullable (i.e., one of the legal values is null). However, even though both ShowDialog and DialogResult are of type bool?, ShowDialog will always return true or false.* Likewise, DialogResult will always be true or false after the dia- log has been closed. Only after a dialog has been shown but before it’s been closed is DialogResult null. This is useful when you’re dealing with a modeless dialog while the dialog itself is still showing. You’ll notice that ShowDialog doesn’t return an enum with OK, Cancel, Yes, No, and so on, like Windows Forms does. ShowDialog indicates only whether the user OK’d the operation of the dialog in some way—what way that was is up to the implementer of the dialog to communicate. Because DialogResult is null while the dialog is shown, WPF checks the DialogResult property after each Window event so that when it transitions to some- thing non-null, the dialog will be closed (see Example 10-15). Example 10-13. Changing the return value of ShowDialog class SettingsDialog : Window { ... void okButton_Click(object sender, RoutedEventArgs e) { // The result from ShowDialog will be true DialogResult = true; Close(); } } Example 10-14. The nullable results of showing a dialog namespace System.Windows { public class Window : ... { ... public bool? ShowDialog( ); public bool? DialogResult { get; set; } ... } * I wish the return type from ShowDialog was just a plain bool to indicate that it can return only true or false and never null. 334 | Chapter 10: Windows and Dialogs As a further shortcut, you can set the IsCancel property on the Cancel button to true, causing the Cancel button to automatically close the dialog without handling the Click event, as Example 10-16 illustrates. In addition to closing the dialog, setting IsCancel to true enables the Esckey as a shortcut to closing the dialog (and setting the DialogResult to false). However, whereas setting IsCancel is enough to cause the dialog to close when the Cancel but- ton is clicked, the corresponding setting on the OK button, IsDefault, isn’t enough to do the same. Transitioning the DialogResult to true, causing the dialog to close, must be handled manually, as shown in Example 10-17. Setting IsDefault provides a visual indication of the default button and enables the Enter key as a shortcut to the OK button (assuming the control with focus doesn’t use the Enter key itself). Data validation Just because the user clicks the OK button doesn’t mean that everything’s OK: the data the user entered generally needs validation. You’ll recall from Chapter 6 that WPF provides per-control validation as part of the binding engine. Dialogs, an exam- ple of which is shown in Example 10-18, are an excellent place to apply this technique. Example 10-15. Closing a modal dialog automatically by changing DialogResult void okButton_Click(object sender, RoutedEventArgs e) { // The return from ShowDialog will be true DialogResult = true; // No need to explicitly call the Close method // when DialogResult transitions to non-null //Close(); } Example 10-16. Cancel buttons transition DialogResult automatically Example 10-17. Default buttons still need to transition DialogResult manually ... ... void okButton_Click(object sender, RoutedEventArgs e) { // Need this to reflect "OK" back to dialog owner DialogResult = true; } Dialogs | 335 In Example 10-18, I’ve added a validation rule to the report folder field that requires it to exist on disk. I’ve also added another field, this time to keep track of who’s reporting the reports. The validation rule for this field is a class that makes sure something is entered in this field. The validation rule implementations, some of which are shown in Example 10-19, should not surprise you. Example 10-18. Data validation and dialogs ... ... ... Example 10-19. Some example validation rules public class FolderMustExist : ValidationRule { public override ValidationResult Validate(object value, ...) { if (!Directory.Exists((string)value)) { return new ValidationResult(false, "Folder doesn't exist"); } 336 | Chapter 10: Windows and Dialogs With these rules in place, as the user makes changes to the fields, the validation rules are fired and the controls are highlighted and tool-tipped with error indications, as shown in Figure 10-7. However, if we don’t make any changes or if we skip some fields before clicking the OK button, the user will have no way of knowing that some of the fields are invalid. Even worse, in the OK button handler, the Window class provides no facilities for man- ually checking all of the bindings to see whether there is any invalid data on the dia- log. I assume a future version of WPF will provide this functionality, but in the meantime, I’ve built a little method called ValidateBindings that provides a first cut at this functionality, which you can use in your own custom dialogs in the OK but- ton handler, as shown in Example 10-20. return new ValidationResult(true, null); } } public class NonZeroLength : ValidationRule { public override ValidationResult Validate(object value, ...) { if (string.IsNullOrEmpty((string)value)) { return new ValidationResult(false, "Please enter something"); } return new ValidationResult(true, null); } } Figure 10-7. An error in a dialog validation rule (Color Plate 19) Example 10-20. Validate all controls in the OK button // This is here 'til future versions of WPF provide this functionality public static bool ValidateBindings(DependencyObject parent) { Example 10-19. Some example validation rules (continued) Dialogs | 337 With our validation helper method in place, when the user clicks the OK button, she gets a notification of all of the fields in error, not just the ones she’s changed or the ones she’s given focus to, as shown in Figure 10-8. Modeless dialogs We’ve been talking about modal dialogs so far, mostly because the similarities between a modal and a modeless dialog outweigh the differences. We still create an instance of a dialog class with style choices that make it look like a dialog. We still pass the data into and out of the dialog with properties. We still validate the data before notifying anyone that it’s available. The only real differences between a modal and modeless dialog is that with a modeless dialog, we need some slightly different UI choices (generally “Apply” and “Close” instead of “OK” and “Cancel”), and we need to fire an event when the Apply button is clicked so that interested parties can pull out validated data. // Validate all the bindings on the parent bool valid = true; LocalValueEnumerator localValues = parent.GetLocalValueEnumerator( ); while( localValues.MoveNext( ) ) { LocalValueEntry entry = localValues.Current; if( BindingOperations.IsDataBound(parent, entry.Property) ) { Binding binding = BindingOperations.GetBinding(parent, entry.Property); foreach( ValidationRule rule in binding.ValidationRules ) { ValidationResult result = rule.Validate(parent.GetValue(entry.Property), null); if( !result.IsValid ) { BindingExpression expression = BindingOperations.GetBindingExpression(parent, entry.Property); Validation.MarkInvalid(expression, new ValidationError(rule, expression, result.ErrorContent, null)); valid = false; } } } } // Validate all the bindings on the children for( int i = 0; i != VisualTreeHelper.GetChildrenCount(parent); ++i ) { DependencyObject child = VisualTreeHelper.GetChild(parent, i); if( !ValidateBindings(child) ) { valid = false; } } return valid; } void okButton_Click(object sender, RoutedEventArgs e) { // Validate all controls if (ValidateBindings(this)) { DialogResult = true; } } Example 10-20. Validate all controls in the OK button (continued) 338 | Chapter 10: Windows and Dialogs Updating our settings dialog to operate modelessly starts with new buttons, as shown in Example 10-21. Handling the button clicks is slightly different, too, as you can see in Example 10-22. Figure 10-8. Validating all controls in the OK button handler (Color Plate 20) Example 10-21. Apply and Close buttons for a modeless dialog Example 10-22. Handling the Apply and Close buttons in a modeless dialog public partial class SettingsDialog : System.Windows.Window { ... public SettingsDialog( ) { ... applyButton.Click += applyButton_Click; closeButton.Click += closeButton_Click; } // Fired when the Apply button is clicked public event EventHandler Apply; void applyButton_Click(object sender, RoutedEventArgs e) { // Validate all controls if (ValidateBindings(this)) { // Let modeless clients know if (Apply != null) { Apply(this, EventArgs.Empty); } Dialogs | 339 In Example 10-22, we use the ValidateBindings helper method, and if everything is valid, we fire the Apply event to let the owner know, keeping the dialog open until the Close button is clicked. In the handler for the Close button, there’s no more automatic closing of the dialog in the modeless case, so we close it ourselves. The owner of the dialog changes a bit as well, as Example 10-23 illustrates. // Don't close the dialog 'til Close is clicked // DialogResult = true; } } void closeButton_Click(object sender, RoutedEventArgs e) { // The IsCancel button doesn't close automatically // in the modeless case Close(); } } Example 10-23. Handling the Apply event on a custom modeless dialog public partial class Window1 : System.Windows.Window { ... Color reportColor; string reportFolder; string reporter; void settingsButton_Click(object sender, RoutedEventArgs e) { // Initialize the dialog SettingsDialog dlg = new SettingsDialog( ); dlg.Owner = this; dlg.ReportColor = reportColor; dlg.ReportFolder = reportFolder; dlg.Reporter = reporter; // Listen for the Apply button and show the dialog modelessly dlg.Apply += dlg_Apply; dlg.Show(); } void dlg_Apply(object sender, EventArgs e) { // Pull the dialog out of the event args and apply the new settings SettingsDialog dlg = (SettingsDialog)sender; reportColor = dlg.ReportColor; reportFolder = dlg.ReportFolder; reporter = dlg.Reporter; // Do something with the dialog properties } } Example 10-22. Handling the Apply and Close buttons in a modeless dialog (continued) 340 | Chapter 10: Windows and Dialogs In Example 10-23, when the user asks to see the settings dialog, it’s initialized as before, but because it’s shown modelessly, we need to know when the user clicks Apply (and the data has been validated), so we subscribe to the Apply event. When that’s fired, we pull the SettingsDialog object back out of the sender argument to the Apply event and pull out our settings as before. The only other thing you might want to do is to set a flag that a settings dialog is being shown so that you don’t show more than one of them. Where Are We? The base class for top-level window functionality is Window, providing the features and flexibility you need to fit in with the rest of the applications on your desktop. In addition, the Window class provides for dialog-style user interactions, both modal and modeless. Finally, the NavigationWindow class, which derives from the Window class, forms the core of most standalone navigation-based applications, which we’ll cover in the next chapter. 341 Chapter 11 CHAPTER 11 Navigation11 One of the mantras of the WPF team was “best of Windows, best of the Web,” which drove much of the innovation in the platform. In the preceding chapter, we looked at windows in a very Windows-centric way, but there’s one innovation that the Web has made popular that we haven’t discussed: navigation between content one page at a time. NavigationWindow The idea of navigation in WPF is that instead of showing multiple windows in a cas- cading style—a popular Windows application style used in the preceding chapter— we show pages of content inside a single frame, using standard navigation meta- phors, like the Back and Forward buttons, to go between pages. If you want to build an application that does this, you can derive from the NavigationWindow class instead of the Window class and navigate to any WPF content you like (see Example 11-1). // Window1.xaml.cs ... using System.Windows.Navigation; // home of the NavigationWindow public partial class Window1 : NavigationWindow { public Window1( ) { InitializeComponent( ); // Navigate to some content Navigate("Hello, World."); } } Example 11-1. Navigation basics 342 | Chapter 11: Navigation In Example 11-1, we’ve defined a custom NavigationWindow that sets its initial content to a string using the Navigate method, which works as you’d expect (Figure 11-1). Notice in Figure 11-1 the presence of the Back and Forward buttons, as well as the little triangle. These controls are provided and enabled/populated as appropriate based on the navigation history. In this case, we’ve navigated only once, so the navi- gation buttons are disabled (there’s nowhere to go backward or forward to). The thing that navigates to our content, displays it, and shows the navigation controls is called a navigation host. The NavigationWindow is one of the three navigation hosts we’ll discuss in this chapter. Pages If we want to get a little fancier than a string, we can create multiple “pages” of con- tent, which is specifically what the Page class was invented for (see Example 11-2). Figure 11-1. The simplest navigation application Example 11-2. Packaging content with a Page object Click to see page 2 // Page1.xaml.cs ... using System.Windows.Controls; // home of the Page public partial class Page1 : Page { public Page1( ) { // Initialize page from XAML InitializeComponent(); } } Pages | 343 To get the basicskeleton of a new Page class, you can right-click on your project in the Visual Studio 2005 Solution Explorer, choosing Add ➝ New Item, and select Page (WPF). Example 11-2 was started that way, adding the WindowTitle, the Title, and the content. The WindowTitle is what shows up in the caption of the navigation host. The Title property is what shows up in the history drop-down. If you don’t set a page’s Title property, it will be composed for you as WindowTitle (foo.xaml), which isn’t particularly friendly. The content in Example 11-2 uses a Hyperlink, which is a nice little element that handles clicking for navigation applications.* We’re setting the NavigateUri property to point to the page resource we’d like it to load for us. The NavigateUri supports the normal URI format (e.g., a URL to an HTTP file on the Web), as well as the pack URI format described in Chapter 12. Page two of our content is another custom Page class defined in XAML, as shown in Example 11-3. // Page2.xaml.cs ... public partial class Page2 : Page { public Page2( ) { InitializeComponent( ); // Handle the button Click event backButton.Click += backButton_Click; } void backButton_Click(object sender, RoutedEventArgs e) { // The Page class provides direct access to navigation services this.NavigationService.GoBack(); } } Example 11-3 looks pretty much like Example 11-2, except that in this case, we’re assigning the hyperlink a name in the XAML so that we can handle the Click event and handle the “go back” navigation as though the user had clicked the Back button (which is enabled as soon as the user has navigated to another page). * We describe the Hyperlink element and its role as part of the WPF text object model in Chapter 14. Example 11-3. Using the navigation service 344 | Chapter 11: Navigation To navigate programmatically, each navigation host provides a navigation service. The navigation service is responsible for fulfilling navigation requests, tracking his- tory, providing events for handling navigation events (e.g., Navigating, Navigated, NavigationFailed, etc.), as well as methods of navigating history (e.g., GoBack, GoForward, Navigate, etc.).* To access the navigation service associated with a dependency object, you can use the static GetNavigationService method of the NavigationService class: public Page2( ) { ... void backButton_Click(object sender, RoutedEventArgs e) { // get the page's navigation service NavigationService navService = NavigationService.GetNavigationService(this); navService.GoBack(); } } As a shortcut, the Page class provides the NavigationService property. In addition, the Page class supports the set of navigation commands (as described in Chapter 4) on the NavigationCommands class (e.g., BrowseBack, BrowseForward, Refresh, etc.). You can use the commands to eliminate the need for any Click event handler code in our example Page 2, as shown in Example 11-4. // Page2.xaml.cs ... public partial class Page2 : Page { public Page2( ) { InitializeComponent( ); } } With our content hosted in pages, we can use the URI trick, shown in Example 11-5, to navigate to the first page from the navigation window. * For a wonderful picture of the navigation events and when they happen, I recommend the SDK topic “Nav- igation Overview,” available at http://msdn2.microsoft.com/en-gb/library/ms750478.aspx#NavigationService (http://tinysells.com/92). Example 11-4. Using navigation commands Pages | 345 In fact, the desire to define an entire application as a set of pages and to simply navi- gate to the first page without any muss or fuss is something that the Application object’s StartupUri property supports directly, removing the need for a main win- dow to host page content at all (see Example 11-6). In the case of a standalone Windows application, the application will create a NavigationWindow for you and navigate to the page specified by the StartupUri prop- erty, as Figure 11-2 shows (after we’ve navigated to the second page).* Notice that setting the Title property on each Page has resulted in the name of the page instead of the WindowTitle property. Example 11-5. Navigating to the first page from the main window // Window1.xaml.cs ... public partial class Window1 : NavigationWindow { public Window1( ) { InitializeComponent( ); // Show first page this.Navigate(new Uri("Page1.xaml", UriKind.Relative)); } } Example 11-6. Navigating to the first page using the StartupUri Figure 11-2. Populating the history with the Title property * In the case of an XBAP, the application will not create a NavigationWindow, as it doesn’t have the permissions in partial trust to do so. Instead, it will create another navigation host that knows how to show your pages just like a page of HTML in Internet Explorer 6+, as you’ll see later, in the “XBAPs” section. 346 | Chapter 11: Navigation Although setting the StartupUri property is a useful shortcut if your application’s main window is going to be navigation-based, nothing is stopping you from using NavigationWindow-like dialogs to build wiz- ards, even if your main window is not navigation-based. The WPF fac- toring of NavigationWindow allows it to be used like any other window. Loose XAML If you’re willing to limit what you put in your XAML (e.g., removing all code-behind files, including the x:Class declaration), stick to only XAML filenames as navigation targets, and so on, you can double-click on XAML files in the shell and navigate between them. Example 11-7 is an updated Page2.xaml to start navigation directly from the shell. Notice that I am no longer using a Button here. This is because the navigation com- mands don’t work from loose XAML and because I have no code-behind file in which to handle the Click event myself. Double-clicking on Page1.xaml and then clicking on the link yields Figure 11-3. Example 11-7. Limitations of loose XAML Click to go back to page 1 Figure 11-3. Navigating loose XAML pages in IE7 Pages | 347 Due to these limitations, navigating between pages of loose XAML is largely a nov- elty. Instead, if you want to host your pages in the browser, you’ll want to package them into an XBAP, discussed later in this chapter. Fragment Navigation If you’re navigating to a page with a great deal of content (for example, a document such as one could construct using the techniques in Chapter 14), you might want to navigate not just to a page, but to a specific section of a page. You can do this with fragment navigation, which you can perform by composing the URI with a trailing fragment identifier, like so: content.xaml#fragmentName The fragment name maps to a named element on the target page. For instance, con- sider Example 11-8, which shows a piece of XAML that defines a longish chunk of text. Example 11-8 includes some named elements. We can refer to these names from a table of contents (see Example 11-9). When navigation is performed against a fragment URI and the section is contained in a navigation target that supports scrolling, the section’s content will be brought into view (or at least as much as will fit into the navigation host), as shown in Figure 11-4, after Topic 4 has been scrolled into view. Example 11-8. A document with names suitable for fragment navigation ... Topic 1 Lorem ipsum dolor sit amet, ... ... ... Topic 1 Topic 2 ... 348 | Chapter 11: Navigation For those of you familiar with HTML name fragment navigation, note that the simi- larity of mechanism is not a coincidence. Because navigation in both WPF and HTML is based on URIs and the URI syntax supports fragments, we get the same syntax for both.* Page Lifetime As you begin to string several pages together, you may begin to wonder about the life- time of a page. For example, consider a very simple guessing game that lets you guess a number, and if you don’t get it in one guess, you lose. The idea is that you can have multiple guesses by backing up and trying again. The implementation of our first page isn’t surprising, as you can see in Example 11-10. Figure 11-4. Fragment navigation * The URI syntax is defined by RFC 2396 and is available at http://www.ietf.org/rfc/rfc2396.txt (http:// tinysells.com/96). Example 11-10. Exploring page state (shh... the answer is .) Guess // Page1.xaml.cs Pages | 349 In the XAML, we’re laying out the elements in a straightforward way, naming the guess and answer text boxes so that we can manipulate them. (Also, notice that we put the answer on the page so that we can see what’s happening to the page’s state as we navigate around.) In the code, when the page is created, we generate a random number, keep it in the page’s state for subsequent guesses, and populate the text box. Figure 11-5 shows the results of showing the first page, navigating to the sec- ond page, and then navigating back. ... public partial class Page1 : Page { int answer = (new Random()).Next( ); public Page1( ) { InitializeComponent( ); answerBox.Text = answer.ToString( ); } ... } Figure 11-5. Navigating and page state Example 11-10. Exploring page state (continued) 350 | Chapter 11: Navigation You’ll notice that the answer the second time the first page is shown is different from the answer the first time. This is because, as a memory usage optimization, the navi- gation services of WPF do their best to keep the smallest amount of data associated with each page as they can get away with. In our case, because we’re navigating between pages using a URI, the navigation services keeps the URI,* throwing away the page object itself (and all of the visuals associated with the page). What this means for us, of course, is that every time the user navigates to the first page, a new Page1 object is created, generating a new answer and making it even more difficult for the user to guess. If you’d like to track the lifetime of a Page, you can do so with the Loaded and Unloaded events, shown in Example 11-11. For example, the navigation sequence in Figure 11-5 looks like this: Page1 constructed Page1_Loaded Page1_Unloaded Page1 constructed Page1_Loaded One other thing that you’ll notice about Figure 11-5 is that although the answer was regenerated along with the Page1 object, the answer text box state was properly restored the second time the first page is shown. This is because the WPF navigation * The navigation service also keeps any data associated with navigation-aware controls using a mechanism we’ll see in a moment. Example 11-11. Page lifetime public partial class Page1 : Page { int answer = (new Random()).Next( ); public Page1() { Debug.WriteLine("Page1 constructed"); InitializeComponent( ); answerBox.Text = answer.ToString( ); Loaded += Page1_Loaded; Unloaded += Page1_Unloaded; } void Page1_Loaded(object sender, RoutedEventArgs e) { Debug.WriteLine("Page1_Loaded"); } void Page1_Unloaded(object sender, RoutedEventArgs e) { Debug.WriteLine("Page1_Unloaded"); } } Pages | 351 services provide all kinds of different ways to keep state between page navigations while still maintaining the optimization of not actually keeping the page:* • Adding to your page a custom dependency property marked with the FrameworkPropertyMetadataOptions.Journal flag. Several of the WPF controls, including TextBox, use this mechanism so that they can restore their state between navigations. • Implementing the IProvideCustomContentState interface on your page, either with or without a corresponding CustomContentState object. For large applications of navigation, you should absolutely take advantage of this navigation optimization (provided by default). Otherwise, the user could just keep navigating around in your application, adding pages to the history that he may never get back to, even though the visuals associated with those pages continue to take up memory. However, for simpler applications, if you would like to turn off this optimization, you can with a flip of the KeepAlive switch (see Example 11-12). The KeepAlive flag defaults to false, which means that the navigation history will attempt to destroy the page object (and all of the associated visuals) if it can, provid- ing the hooks I listed to keep track of state between navigations. However, in certain cases, the navigation optimization can’t be applied. For example, if we call the Navigate method with an object instead of a URI, the navigation service doesn’t know how to re-create the object, so it caches it instead, which has the same effect as setting KeepAlive to true manually, as we did in Example 11-12. Keeping data between navigations to a single page is only part of the story. If you’re going to implement the second page that checks the answer, we’ll need to pass it and the user’s current guess from the first page. Passing Data Between Pages It’s easy enough to define our Page2 class with a couple of properties to accept incoming data, as shown in Example 11-13. * The various techniques for keeping state between page navigations are discussed in detail in the SDK topic “Nav- igation Overview,” available at http://msdn2.microsoft.com/en-gb/library/ms750478.aspx#NavigationService (http://tinysells.com/92). Example 11-12. Setting KeepAlive to true ... 352 | Chapter 11: Navigation KeepAlive = False + Data Binding Considered Harmful If you’re planning to use data binding in your pages, you should set KeepAlive to True. Unfortunately, out of the box, the navigation optimization doesn’t work with data binding and will not restore data binding options properly on instantiations. Because data binding is so darn useful (not only is it the foundation of keeping data in sync between your data objects and your UI, but it’s also how the validation and data tem- plates features are exposed, among others), it’s likely you’ll feel the tension between them, picking one or the other on any single page, but not both. The good news is that, as of this writing, this is a high-priority issue scheduled to be fixed in the next version of the .NET Framework (code name “Orcas”) as well as the next service pack for .NET 3.0. Example 11-13. Accepting data into a page via properties You guessed: Try Again // Page2.xaml.cs ... public partial class Page2 : Page { public Page2( ) { InitializeComponent( ); Loaded += Page2_Loaded; backButton.Click += backButton_Click; playAgainLink.Click += playAgainLink_Click; } int answer; public int Answer { get { return answer; } set { answer = value; } } int guess; public int Guess { get { return guess; } set { guess = value; } } Pages | 353 In Example 11-13, we’re defining two properties to be passed in from the first page—the answer we’re looking for and the current guess.* When the page is loaded, we use those values to populate the UI. You’ll also notice that we’re not setting KeepAlive to anything in Page2.xaml.By default, it’s False, but that setting will be ignored because we’re navigating to the page as an object and not as a URI (see Example 11-14). Figure 11-6 shows the state of an incorrect guess, and Figure 11-7 shows the history after a couple of successes. The technique of passing in parameters directly to a new page object works fine, especially when you’ve got several instances of the same object to keep track of. void Page2_Loaded(object sender, RoutedEventArgs e) { guessBlock.Text = guess.ToString(); if( answer == guess ) { resultBlock.Text = "You win!"; } else if( answer < guess ) { resultBlock.Text = "Guess lower..."; } else { resultBlock.Text = "Guess higher..."; } } void backButton_Click(object sender, RoutedEventArgs e) { // Let them guess again NavigationService.GoBack( ); } void playAgainLink_Click(object sender, RoutedEventArgs e) { // Start a new game NavigationService.Navigate(new Uri("Page1.xaml", UriKind.Relative)); } } * Giving the Page2 class a constructor that takes arguments instead of passing them in via properties would work as well. Example 11-14. Passing data to a page // Page1.xaml.cs ... public partial class Page1 : Page { ... void guessLink_Click(object sender, RoutedEventArgs e) { Page2 page2 = new Page2(); page2.Answer = answer; page2.Guess = int.Parse(guessBox.Text); NavigationService.Navigate(page2); } } Example 11-13. Accepting data into a page via properties (continued) 354 | Chapter 11: Navigation However, sometimes you’d like to keep more “global” state (i.e., state that spans even multiple instances of a particular page type). For example, it would be inconve- nient to have to pass the count of games played through every single page, not least because we’d have to stop navigating to the first page by URI, instead passing in a parameter. For these situations, WPF has provided the Properties dictionary on the Application, shown in Example 11-15. Figure 11-6. Guessing incorrectly Figure 11-7. The results of guessing correctly in the history Example 11-15. Keeping track of wins in the application’s Properties collection // Page2.xaml.cs ... public partial class Page2 : Page { ... void Page2_Loaded(object sender, RoutedEventArgs e) { guessBlock.Text = guess.ToString( ); Pages | 355 In Example 11-15, we’re tracking the number of games won by using a key of GamesWon and incrementing it on every win. The Properties dictionary is an object-to- object mapping, so you can keep whatever you want in there. By using a string, and a short one at that, we’re risking the possibility of stepping on someone else’s data, which is the problem with global data in general. Page Functions In the world of standard Windows applications, if you want to ask the user a quick question without disturbing the rest of your careful arrangement of visuals and windows, you simply pop up a modal dialog and ask ‘im. However, in the world of navigation-based applications, external windows of any kind are considered rude at the very least (remember the pop-ad craze of the early 2000s?) and verboten in the worst case (XBAPs don’t allow pop-up windows). So, the question is, how do we ask the user a quick question, returning him to whence he came, none the worse for wear? The answer is page functions. A page function is a page that you call like a function, passing in input and getting output as desired. When the page function returns, the return value is provided to the calling page, where it can pick up where it left off. You can think of page func- tions as the modal dialog equivalent in navigation-based applications. As a simple example, let’s imagine that we wanted the user to say the magicword before she is allowed to play the guessing game. The UI for our page function looks like Figure 11-8. Our page function to ask the user for the magic word looks like a page, but with a few minor differences, as shown in Example 11-16. if( answer == guess ) { resultBlock.Text = "You win!"; TrackWin(); } else ... } // NOTE: uniqueness testing to make sure that every won game // is only tracked once is left as an exercise to the reader // (Send answers to csells@sellsbrothers.com...) void TrackWin() { IDictionary properties = Application.Current.Properties; if( !properties.Contains("GamesWon") ) { properties["GamesWon"] = 0; } properties["GamesWon"] = (int)properties["GamesWon"] + 1; } } Example 11-15. Keeping track of wins in the application’s Properties collection (continued) 356 | Chapter 11: Navigation The skeleton for Example 11-16 was generated in Visual Studio 2005 by right-clicking on the project, choosing Add ➝ New Item, selecting PageFunction (WPF), entering a name, and clicking the OK button. Notice that Example 11-16 has a PageFunction ele- ment at the root to match the PageFunction base class name. However, because the PageFunction class is generic, we set the x:TypeArguments property to the type argu- ment to use to construct the generic PageFunction type.* The type passed will be the type of the result from our page “function call.” The code needs to have a matching type argument, as shown in Example 11-17. Figure 11-8. A page function UI Example 11-16. Declaring a page function Play Quit * The x:TypeArguments property is XAML’s nod to generics and works only on elements at the root of a XAML document. Example 11-17. Implementing a page function // MagicWordPageFunction.xaml.cs ... public partial class MagicWordPageFunction : PageFunction { public MagicWordPageFunction( ) { InitializeComponent( ); playLink.Click += playLink_Click; Pages | 357 In addition to taking in the magic word to check for as a property (just like our page example earlier), we’re checking the word the user enters when she clicks on the Play link. If the word is sufficiently magic, we return from the page function by calling the OnReturn method provided by the PageFunction base class, passing the word the user entered so that the caller of the page function can inspect it. This is the page function equivalent of setting a modal dialog’s DialogResult to true, and will trigger the page function to remove itself from the history and return to the caller.* In addition, we’re storing the magicword the user entered into the application’s Properties function so that she won’t have to enter it again (as you’ll see). On the other hand, if the user clicked the Quit link, we call OnReturn, passing null to indicate the equivalent of the user clicking the Cancel button on a modal dialog, also returning to the caller. quitLink.Click += quitLink_Click; Loaded += MagicWordPageFunction_Loaded; } string magicWord; public string MagicWord { get { return magicWord; } set { magicWord = value; } } void playLink_Click(object sender, RoutedEventArgs e) { // Check to see if the magic word is the right one if( wordBox.Text == magicWord ) { OnReturn(new ReturnEventArgs(wordBox.Text)); Application.Current.Properties["MagicWordEntered"] = wordBox.Text; } } void quitLink_Click(object sender, RoutedEventArgs e) { OnReturn(null); // Cancel } void MagicWordPageFunction_Loaded(object sender, RoutedEventArgs e) { if( Application.Current.Properties.Contains("MagicWordEntered") && (string)Application.Current.Properties["MagicWordEntered"] == magicWord ) { // No need to re-enter the magic word for subsequent games OnReturn(new ReturnEventArgs(magicWord)); } } } * It often makes the most sense for a page function’s page to be removed from the navigation history when it returns, just like a modal dialog removes itself from the screen. However, if you’d prefer to leave it in, you can set the page function’s RemoveFromJournal property to false (it defaults to true). Example 11-17. Implementing a page function (continued) 358 | Chapter 11: Navigation Finally, so that the user doesn’t have to enter the magicword more than once—no matter how many times the page function is navigated to in the application’s life- time—in the page function’s Loaded event, we check for the presence of the magic word in the application’s Properties collection, calling OnReturn right away if the user has already entered it. Calling the page function from a “zeroth” page I’ll show you presently looks like Example 11-18. Page Functions and KeepAlive If you look at Example 11-16, you’ll notice that we’re not setting the KeepAlive prop- erty at all. Just like a Page,aPageFunction class will default the KeepAlive property to false. Further, even though we navigate to a page function by object instead of by URI, the WPF navigation service uses magic to figure out how to tear it down and rebuild it between navigations. This means that you’ll have to keep in mind all of the KeepAlive issues mentioned earlier, but because page functions are meant to be short-lived, there is less chance of a memory usage problem if you want to set KeepAlive to true. (All of the page function’s visuals will be torn down by default when you call OnReturn.) Example 11-18. Using a page function // Page0.xaml.cs ... public partial class Page0 : Page { ... void playLink_Click(object sender, RoutedEventArgs e) { MagicWordPageFunction fn = new MagicWordPageFunction(); fn.MagicWord = "please"; fn.Return += fn_Return; NavigationService.Navigate(fn); } void fn_Return(object sender, ReturnEventArgs e) { // Get the navigation service from the sender // (the current page's hasn't yet been restored and // this.NavigationService is null NavigationService navService = ((PageFunctionBase)sender).NavigationService; // User canceled if( e == null ) { navService.Navigate(new Uri("QuitterPage.xaml", UriKind.Relative)); } // Double-check the magic word else if( e.Result == "please" ) { navService.Navigate(new Uri("Page1.xaml", UriKind.Relative)); Frames | 359 At the click of a hyperlink, we create an instance of the page function, passing in the preferred magicword, * subscribing to the Return event (for the page equivalents of both “OK” and “Cancel”), and navigating to the page function just as though it were a normal page (and in fact, Page is in the inheritance hierarchy of the PageFunction class). In the Return event handler, the first thing we do is grab the current navigation ser- vice from the sender. Unfortunately, at this point in the action, the NavigationService property of the page function caller hasn’t yet been set, so we have to rely on the one from the page function itself (the sender). Next, we check to see whether the ReturnEventArgs (where T is String in our case) event argument is null. If it is, the page function called OnReturn passes null, and we should respond appropriately. On the other hand, if the return event argument isn’t null, we can check the Result property for the data passed to OnReturn. In our example, we double-check that it was indeed the magic word we were looking for and navigate to the first page of our guessing game. Figure 11-9 shows a nominal navigation session. Clicking the Play link on the Welcome page causes the magic word page function to show and take its answer. When that returns, the Welcome page navigates to the first page of our guessing game. Notice that the history in Figure 11-9 doesn’t show the magic word page function at all. Further, because the magic word page function keeps track of whether the magic word was already entered and short-circuits itself as appropriate, if we were to go back to the Welcome page and click the Play link again, the magicword UI would never show, the Return event handler would be fired immediately, and the user would go directly to the guessing page. Frames Thus far, we’ve spent a lot of time talking about the NavigationWindow, how it han- dles navigation, and how it integrates with pages and page functions. However, the navigation window is but one navigation host. A navigation host in WPF is anything that provides navigation support. Besides the navigation window, which provides top-level window navigation support, WPF also provides the Frame, for contained navigation support. For example, nothing is stopping us from hosting our guessing game in a frame, which is itself contained by something else, as shown in Example 11-19. } } } * You were perhaps expecting “abracadabra”? Example 11-18. Using a page function (continued) 360 | Chapter 11: Navigation In Example 11-19, we’re hosting a Frame in a window, but you can host it equally well in a page. The main property you’ll care about on the Frame class is the Source, which indicates where you’d like to start navigation. Figure 11-10 shows the results of making one guess on the history for the frame. Frames are useful when you’d like to add navigation to part of your window (or to multiple parts), but you don’t want the entire window dedicated to it. For example, your average web site is composed of a set of content that goes inside a navigation frame, including menus, graphics, and so on. The Frame element is one way to imple- ment the content inside the outer navigation frame. Figure 11-9. A page function in action Example 11-19. Using a frame navigation host XBAPs | 361 XBAPs The final navigation host that WPF provides is an internal class called RootBrowserWindow. Like NavigationWindow and Frame, the RootBrowserWindow knows how to host content for navigation. However, RootBrowserWindow does it by integrating with versions 6 and later of Internet Explorer* in order to implement XAML Browser Applications (XBAPs). An XBAP is a WPF application with these characteristics: • Hosted in IE6+ like loose XAML pages (although they’re compiled), whether at the top level or inside an IFRAME. In fact, you’re meant to be able to click back and forth between HTML and XBAPs without knowing that you’re doing so (except that the XBAP pages are “better”). • No custom top-level windows. You must use the RootBrowserWindow provided and no other custom top-level windows (e.g., custom dialogs). • Runs in partial trust that can’t be elevated by users like normal ClickOnce applications. • Can be deployed like ClickOnce “online-only” applications. The standard Click- Once “offline/online” deployment is available if your main window is a NavigationWindow, but it won’t be hosted in Internet Explorer. You can get a new XBAP application skeleton in Visual Studio 2005 by choosing the “XAML Browser Application (WPF)” project template. It will give you a standard navigation application without any window definition, just a page. The chief differ- ence between an XBAP and a standard navigation-based application is the HostInBrowser property set in the project file: true Figure 11-10. Using a frame navigation host * Only IE7+ has an integrated navigation UI. 362 | Chapter 11: Navigation ... ... In addition, an XBAP’s ClickOnce manifests must be signed to build, which will be set up for you when you use Visual Studio 2005’s project template. In fact, for the pur- poses of testing and debugging, you can execute an XBAP directly from Visual Studio 2005 (using Debug ➝ Start Debugging or Debug ➝ Start Without Debugging) to see it running inside the browser without first publishing, as shown in Figure 11-11. Notice that after a guess, the IE7 history looks pretty much like we’d expect from both the navigation window and the frame. XBAP Publication and Deployment The publication of an XBAP happens exactly like the publication of a WPF applica- tion via ClickOnce, as discussed in the Chapter 2. (I’ll wait here while you refresh your memory.) Right-clicking on your XBAP project and choosing Publish brings up the Publish Wizard, which leads you through the publication process. Unlike the Publish Wizard for standalone ClickOnce applications, this time you won’t get a publish.htm, but here’s a template to get you started: Welcome to XBAP Fun! XBAP Fun! Notice that the link to your XBAP ends in .xbap, unlike a standalone ClickOnce application, which ends in .application. Further, if you surf to this publish.htm file Figure 11-11. Hosting an XBAP in IE7 Navigation to HTML | 363 and click on the link, you’ll get the download progress as you expect, but then noth- ing else (no security dialog) before the application shows itself. In fact, XBAPs are true “one-click” deployment, regardless of whether you’ve run the application before.* In addition, because we’re surfing to it via URLs, the histories of both XBAP and HTML are merged, as shown in Figure 11-12. XBAPs are your “best of the Web, best of Windows” WPF deployment mode of choice (assuming you can live with the limitations laid out earlier). Navigation to HTML To further drive home the integration between WPF navigation and Internet Explorer, if you navigate to an HTML URL inside of a navigation host, the core OLE control that hosts HTML in IE will be used to show the content. For example: sellsbrothers.com * Of course, that “one click” works only if your XBAP doesn’t try to get more permissions than it’s been awarded, in which case, you can click all day long and it still ain’t gonna run. Figure 11-12. Mixing XAML and HTML in a single navigation history KeepAlive and XBAPs Although the rules about KeepAlive and XBAPs still apply, there is one more wrinkle. If you navigate away from an XBAP and navigate back, the pages will have been flushed regardless of your KeepAlive settings. 364 | Chapter 11: Navigation If you do this from within a standalone application using the navigation window or frame hosts, the HTML page will become part of the history along with everything else. If you do this within an XBAP, however, a new instance of IE will be spun up to handle the navigation (it’s just too weird to host IE inside an XBAP hosted inside IE). Where Are We? Building on base Window functionality, the NavigationWindow forms the core of most standalone navigation-based applications, with Frame for navigating content while controlling the chrome, and RootBrowserWindow for providing XBAP Internet Explorer 6+ navigation integration. 365 Chapter 12 CHAPTER 12 Resources12 WPF offers us great flexibility in how we construct an application’s user interface. But with great power comes great responsibility—we must avoid bewildering the user with a garish and inconsistent frontend. Styles and templates allow us to take control of our application’s visuals, but these features depend on the resource sys- tem in WPF to make it easy to build visually consistent applications without sacrific- ing flexibility. If you want to build a graphically distinctive application, the resource system provides a straightforward way to skin your applications with customized yet consistent visuals. But by default, the resource mechanism simply ensures consis- tency with the system-wide OS theme chosen by the user. In this chapter, we will look at how the resource system lets us plug in visual fea- tures where they are needed. Not only will we see how to ensure that the right look and feel is applied to our application at runtime, but we will also look at how the resource system lets you reuse objects or groups of objects such as drawings. Fur- thermore, we will look at how to manage binary resource streams and how to local- ize applications. Creating and Using Resources The term resource has a very broad meaning—in WPF, any object can be a resource. A brush or a color used in various parts of a user interface could be a resource. Snip- pets of graphics or text can be resources. An object does not have to do anything spe- cial to qualify as a resource. The resource handling infrastructure is entirely dedicated to making it possible to get hold of the resource you require, and it doesn’t care what the resource is. It simply provides a mechanism for identifying and locating objects. At the heart of resource management is the ResourceDictionary class. Outwardly, this is just a simple collection class. It behaves much like an ordinary Hashtable—it allows objects to be associated with keys, and it provides an indexer that lets you retrieve those objects using these keys. So, in theory, you could use the ResourceDictionary like a Hashtable, as Example 12-1 shows. 366 | Chapter 12: Resources In practice, you will not often create your own ResourceDictionary in this way. Instead, you will normally use ones provided by WPF. For example, the FrameworkElement base class, from which most user interface elements derive, pro- vides a resource dictionary in its Resources property. The calls to Add in Example 12-1 illustrate the usual way to add resources from code-behind files, but this dictionary can also be populated from markup, as Example 12-2 shows. The x:Key attribute specifies the key that identifies the resource in the dictionary. It is equivalent to the first parameter of the calls to Add in Example 12-1. You can use any object as a key. Strings are the most common choice, although distinct object instances are often used to identify very broadly scoped resources, such as system resource. When you use compiled XAML to populate a resource dictionary, WPF defers creation of the resources. It leaves each resource in its seri- alized form (known as BAML), and expands this into real objects only on demand. This can significantly improve the startup time for a user interface in cases where not all of the objects are needed as soon as the UI appears. For the most part, this optimization will not have any direct effect on your code’s behavior other than speeding it up. How- ever, if there is something wrong with your markup, this deferred cre- ation can cause the resulting errors to emerge later than you might have expected. Example 12-3 shows code retrieving the resources defined in Example 12-2. Example 12-1. Naïve ResourceDictionary programming ResourceDictionary myDictionary = new ResourceDictionary( ); myDictionary.Add("myBrush", Brushes.Green); myDictionary.Add("HW", "Hello, world"); Console.WriteLine(myDictionary["myBrush"]); Console.WriteLine(myDictionary["HW"]); Example 12-2. Populating a ResourceDictionary from XAML Hello, world Creating and Using Resources | 367 This code accesses the ResourceDictionary using this.Resources. This is all very well for the code-behind file for the markup that defined the resources. However, it is not always this convenient to get hold of the right dictionary. What if we want to define resources accessible to all windows in the application? It would be both tedious and inefficient to copy the same resources into every window in the application. And what if we want a custom control to pick up resources specified by its parent win- dow, rather than baking them into the control? To solve these problems, and to make it easy to achieve consistency across your user interface, FrameworkElement extends the basic ResourceDictionary facilities with a hierarchical resource scope. Resource Scope As well as providing a ResourceDictionary for every element, FrameworkElement also provides a FindResource method to retrieve resources. Example 12-4 shows the use of this to retrieve the same resources as Example 12-3. This may seem pointless—why does this FindResource method exist when we could just use the dictionary’s indexer as we did in Example 12-3? The reason is that FindResource doesn’t give up if the resource is not in the specified element’s resource dictionary. It will search elsewhere. Example 12-5 illustrates the difference between these two approaches. This code uses the myGrid element from Example 12-2 instead of this. The Grid doesn’t have any resources, so the b1 variable will be set to null. However, because b2 is set using FindResources instead of the resource dictionary indexer, WPF considers all of the resources in scope, not just those directly set on the Grid. It starts at the Grid element, but then examines the parent, the parent’s parent, and so on, all the way to the root ele- ment. (In this case, the parent happens to be the root element, so this is a short search. But in general, it searches as many elements as it needs to.) The result is that the b2 vari- able is set to the same Brush object as was retrieved in Examples 12-3 and 12-4. Example 12-3. Retrieving resources from an element’s ResourceDictionary the wrong way // NOT the best way to retrieve resources Brush b = (Brush) this.Resources["myBrush"]; String s = (String) this.Resources["HW"]; Example 12-4. Using FindResource Brush b = (Brush) this.FindResource("myBrush"); String s = (String) this.FindResource("HW"); Example 12-5. FrameworkElement.Resources versus FindResource // Returns null Brush b1 = (Brush) myGrid.Resources["myBrush"]; // Returns SolidColorBrush from Window.Resources Brush b2 = (Brush) myGrid.FindResource("myBrush"); 368 | Chapter 12: Resources It doesn’t stop here. If FindResource gets all the way to the root of the UI without find- ing the specified resource, it will then look in the application. Not only do all frame- work elements have a Resources property, so does the Application object. Example 12-6 shows how to define application-scope resources in markup. (If you are using the nor- mal Visual Studio WPF project template, you would put this in the App.xaml file.) The application scope is helpful for objects that are used throughout your applica- tion. For example, if you use styles or control templates, you would typically put these in the application resources, to ensure that you get a consistent look across all the windows in your application. Resource searching doesn’t even stop at the application level. If a resource is not present in the UI tree or the application, FindResource will finally consult the system scope, which contains resources that represent system-wide settings, such as the con- figured color for selected items, the correct width for a scroll bar, and styles for built- in controls. The control styles WPF adds to the system scope will be based on the user’s chosen “theme” (or “visual style”). Figure 12-1 shows a typical hierarchy of resource sources. Several applications are running, each application may have several windows, and each window has a tree consisting of multiple elements. If FindResource is called on the element labeled “1” in the figure, it will first look in that element’s resource dictionary. If that fails, it will keep working its way up the hierarchy through the numbered items in order, until it reaches the system resources. WPF uses the system scope to define brushes, fonts, and metrics that the user can configure at a system-wide level. The keys for these are provided as static properties of the SystemColors, SystemFonts, and SystemParameters classes, respectively. (These classes define more than 400 resources, so they are not listed here—consult the SDK documentation for each class to see the complete set.) Example 12-7 uses the system scope to retrieve a brush for the currently configured tool tip background color. (See Chapter 13 for more information on brushes.) Example 12-6. Resources at application scope Creating and Using Resources | 369 These system resource classes use objects rather than strings as resource keys. This avoids the risk of naming collisions—system resources are always identified by a specific object, so there will never be any ambiguity between them and your own named resources. Defining custom system-scope resources All the built-in controls rely on the system scope to provide styles and templates suit- able for the current OS theme. Without these resources, the controls would have no appearance by default. If you are writing a custom control, you will usually want to do the same thing. By providing a style for your control, you ensure that it has a default appearance. By putting that style into the system scope, you give developers the opportunity to customize the control by putting an alternative style in a nar- rower scope such as the application scope. Figure 12-1. Resource hierarchy Example 12-7. Retrieving a system scope resource Brush toolTipBackground = (Brush) myGrid.FindResource(SystemColors.InfoBrushKey); System resources Application resources Window resources Element resources Element resources Element resources Element resources Element resources 6 5 4 3 2 1 Application Window 370 | Chapter 12: Resources To add custom resources to the system scope, you must annotate your component with the ThemeInfo custom attribute. This indicates two things: whether your compo- nent has non-theme-specific system-scope resources, and whether it has theme-specific system-scope resources. As Example 12-8 shows, this is an assembly-level attribute. It would typically go in the AssemblyInfo.cs source file. This example declares that the component has generic resources, but no theme- specific resources. This instructs WPF to look in the component for an embedded ResourceDictionary called themes\generic.xaml, and to add any resources in that dictionary to the system scope. (We will show how to embed resource streams later in this chapter.) You can also specify that theme-specific resources are present by setting the first parameter of the attribute to SourceAssembly. This would cause WPF to look for an embedded resource named after the currently selected theme. Table 12-1 shows the names of the embedded resources WPF will look for. If it cannot find a resource for the current theme, it will fall back to the generic resources instead. The ResourceDictionaryLocation enumeration has one more value besides the two shown in Example 12-8: ExternalAssembly. This will cause WPF to look in a separate assembly for the resources. It will look for an assembly with a name formed by adding a period and then the current theme name to your assembly’s name (which means you can specify this only for the theme-specific resources). It uses only the base theme name, without the color scheme appended. For example, if your component is called MyLibrary, and the user is running with the Windows Vista Aero theme, WPF will look for a MyLibrary.Aero component containing the themes\Aero.NormalColor.xaml resources. Example 12-8. Declaring custom system-scope resources [assembly:ThemeInfo( ResourceDictionaryLocation.None, // Theme-specific resources ResourceDictionaryLocation.SourceAssembly // Generic resources )] Table 12-1. Themes and resource names Theme Embedded resource name Aero (Windows Vista) themes\Aero.NormalColor.xaml Luna Blue (Windows XP) themes\Luna.NormalColor.xaml Luna Silver (Windows XP) themes\Luna.Metallic.xaml Luna Olive Green (Windows XP) themes\Luna.Homestead.xaml Royale (Windows Media Center) themes\Royale.NormalColor.xaml Classic (Any Windows version) themes\Classic.xaml Creating and Using Resources | 371 For WPF to be able to find a custom system-scope resource, you must use a suitable key type: the key must incorporate information about which assembly contains the resource. For a custom control’s style, you will normally use the control’s Type object as a key. You typically do this by specifying a TargetType, as in Example 12-9. This automatically uses that type as the key, as well as the style’s target type. When looking up a resource by type, WPF will locate the assembly referred to by the Type object’s Assembly property. If the assembly has a ThemeInfo attribute indicating that system-scope resources are present, WPF will look for an embedded resource dic- tionary. In short, you simply add the ThemeInfo attribute, and put the resource streams in the same component as the custom control, and it all just works. Sometimes it’s useful to put resources other than styles into the system scope. You can do this, but you can’t use a string to name the resource—a simple string won’t tell WPF in which assembly it should be looking. WPF therefore provides the ComponentResourceKey type. This is a class designed to be used as a resource name. It incorporates both an identifier (which may be a string) and a Type object to indicate which assembly defines the type. WPF also defines a corresponding markup exten- sion, offering a syntax for using these keys from XAML, which is shown in Example 12-10. With a resource defined this way in your themes\generic.xaml, or in one of the theme-specific dictionaries, you can refer to the resource using the same syntax that Example 12-10 uses to name it. Because the ComponentResourceKey incorporates a type object, WPF will know which assembly defines the resource, and will be able to find it. Using system-scope resources The system resource classes also define static properties that let you retrieve the rele- vant object directly rather than having to go via the resource system. For example, SystemColors defines an InfoBrush property that returns the same value that FindResource returns when passed SystemColors.InfoBrushKey. So rather than writ- ing the code in Example 12-7, we could have written the code in Example 12-11. Example 12-9. Style using a Type object as key Example 12-10. Naming custom system-scope resources 372 | Chapter 12: Resources When writing code, these properties are likely to be simpler to use than the resource system. However, using the resource key properties offers three advantages. First, if you want to let the user change your application’s color scheme away from the system- wide default, you can override these system settings by putting resources into the application scope. Example 12-12 shows an application resource section that defines a new application-wide value for the InfoBrushKey resource. This replacement value would be returned in Example 12-7, but not in Example 12-11. This is because in Example 12-11, SystemColors has no way of knowing what scope you would like to use, so it always goes straight to the system scope. The second advantage offered by resource keys is that they provide a straightforward way of using system-defined resources from markup. Third, you can make your application respond automatically to changes in system resources. Both of these last two benefits come from using resource references. Resource References So far, we have seen how to retrieve the current value of a named resource in code. Because we usually use resource values to set element properties, we will now look at how to set an element’s property to the value of a resource. This may seem like a ridiculously trivial step. You might expect it look like Example 12-13. This is fine for some resource types, but here it will work only up to a point—it will successfully set the Background property to a brush that paints with whatever the cur- rently selected color for control backgrounds is at the moment when this line of code runs. However, if the user changes the system color scheme, this Background prop- erty will not be updated automatically. The code in Example 12-13 effectively takes a snapshot of the resource value. The code in Example 12-14 does not suffer from this problem. Instead of taking a snapshot, it associates the Background property with the resource. Example 12-11. Retrieving a system resource through its corresponding property Brush toolTipBackground = SystemColors.InfoBrush; Example 12-12. Application overriding system colors // (Hypothetical function for retrieving settings) Color col = GetColorFromUserSettings( ); Application.Current.Resources[SystemColors.InfoBrushKey] = new SolidColorBrush(Colors.Red); Example 12-13. How not to use a system resource value this.Background = (Brush) this.FindResource(SystemColors.ControlBrushKey); Creating and Using Resources | 373 Unlike Example 12-13, if the system resource value changes, the property will auto- matically receive the new value. The practical upshot of this is that if the user changes the color scheme using the Display Properties Control Panel applet, Example 12-14 will ensure that your user interface is updated automatically. WPF defines markup extensions that are the XAML equivalent of the code in the previ- ous two examples. (See Appendix A for more information on markup extensions.) These are the StaticResource and DynamicResource extensions. If you are using a system resource, or any other resource that might change at runtime, choose DynamicResource. If you know the resource will never change, use StaticResource, which takes a snap- shot, avoiding the costs associated with tracking the resource for changes. (The cost is small, but you may as well avoid it for resources that never change.) Example 12-15 shows the use of both resource reference types. A StaticResource reference must appear after the resource to which it refers. Forward references are not allowed. The top-level Window defines a brush as a resource named myBrush. The TextBlock uses this for its Background property via a StaticResource reference. This has a simi- lar effect to the code in Example 12-13. It takes a snapshot, and is appropriate for resources that will not change while the application runs. The grid’s Background has been set to the system “control” color. (This is typically battleship gray—the color often used as the background for dialogs.) Because this is a user-configurable color and could therefore change at runtime, we’ve used a DynamicResource, which has the same effect as the call to SetResourceReference in Example 12-14. Example 12-14. Self-updating system resource reference this.SetResourceReference(Window.BackgroundProperty, SystemColors.ControlBrushKey); Example 12-15. Using resources from markup Hello! 374 | Chapter 12: Resources The DynamicSource syntax is a little more complex than for the StaticResource. This complexity is not because we are using DynamicResource. It is because the resource we wish to use is identified by an object, returned by the static SystemColors. ControlBrushKey property. We could have tried this: This is syntactically correct, but doesn’t do what we want. It will be interpreted as a dynamic reference to a resource named by the string SystemColors.ControlBrushKey. There is no such resource, so the background will not be set. To use the real resource key (the object returned by the ControlBrushKey staticproperty), we have to use the x:Static markup extension as Example 12-15 does—this tells the XAML compiler that the text should be treated as the name of a static property, not as a string. Reusing Drawings It is often useful to put drawings and shapes into resources. There are two main rea- sons for doing this. One is that drawings can be quite complex, and putting them inline as part of the main markup for a user interface can make the XAML hard to read. By putting drawings into the resources section, or even a separate file, the over- all UI structure can be clearer. Another reason is to enable reuse; you may want to use the same graphic in multiple places. Performance can also be a factor—reusing drawing resources is much more efficient than duplicating them. You can represent shapes and drawings in many different ways, as Chapter 13 shows, and all of them can be used as resources. Example 12-16 defines an Ellipse resource called “shape.” It also shows how to use the resource. The StaticResource element here will be replaced at runtime with the resource it names. The result will look like Figure 12-2. There is a problem with this technique: if you use any element that derives from FrameworkElement as a resource, by default you can reference it only once. The rea- son for this restriction is that FrameworkElement is the basis of the user interface tree. Example 12-16. Using a FrameworkElement resource ... Creating and Using Resources | 375 An element knows what its parent is and what children it has, so it is not possible for it to be in more than one place in the tree—its Parent property can point to only one element, after all. So if you were to add a second reference to the ellipse in Example 12-16, WPF would throw an exception complaining that the ellipse is already in use. However, there is a simple solution to this. By default, when you use a resource, you are not using a copy of the object, you are using the object itself, but you can change this behavior with the x:Shared attribute. Example 12-17 shows a modified version of Example 12-16. By enabling sharing of the ellipse resource, we can use the resource as many times as we like. WPF will now build a new copy of the resource each time you use it.* As Figure 12-3 shows, this enables us to use the ellipse multiple times over. This is effective, but it is not the most efficient approach available, because it builds a new copy of the resource for each reference. For simple graphics this will not be a problem. How- ever, if you are working with complex drawings containing many hundreds or even thousands of elements, the overhead of copying for each use can introduce perfor- mance problems. Figure 12-2. Reference to element resource Example 12-17. Disabling sharing ... * Copies are built using the deferred resource-loading mechanism described earlier—WPF goes back to the BAML each time it makes a new copy. Consequently, you can use this technique only in compiled XAML. It will not work in XamlPad because that parses the XAML at runtime. 376 | Chapter 12: Resources The Drawing classes, such as GeometryDrawing or DrawingGroup, are better candidates for storing drawings as resources. Because Drawing does not derive from FrameworkElement, you are free to use one instance in multiple places. DrawingGroup lets you put as many shapes and images into a single drawing as you like, and the various other types derived from Drawing provide access to all of WPF’s graphics facilities. (See Chapter 13 for more details.) Example 12-18 shows how to define and use a drawing resource. It uses a DrawingBrush to display the Drawing. Figure 12-4 shows the result. Figure 12-3. Multiple references to a single element resource Example 12-18. Using a Drawing resource ... Creating and Using Resources | 377 You can also define the DrawingBrush as a resource. This moves some of the complex- ity into the Resources section, making the markup considerably simpler at the point at which you use the resource, as Example 12-19 shows. The results are the same as the preceding example, as shown in Figure 12-4, but the markup that uses the resource is just one line long instead of five. If you want the same shape to appear in multiple drawings, you might want to drop down a level and use individual geometry objects as resources. You can then refer to these from within drawings. Example 12-20 shows the use of a DrawingBrush with a GeometryDrawing that uses an EllipseGeometry resource. (Because this is yet another ellipse, we won’t waste your time with another picture—it’ll look much like Figure 12-4, only in cyan.) Figure 12-4. References to Drawing resource Example 12-19. Using a DrawingBrush resource ... Example 12-20. Using a Geometry resource ... 378 | Chapter 12: Resources In this particular example, the use of resources may seem a little extreme—it would probably have required less effort just to create a new geometry from scratch. How- ever, some geometries, such as PathGeometry, can become quite complex, in which case this kind of reuse makes more sense. Although drawings and geometries are powerful, reusable, and lightweight, they have one disadvantage. They are not framework elements, so they cannot take advantage of WPF’s layout system. You can scale them using the brush scaling fea- tures described in Chapter 13, but drawings cannot contain framework elements, so you cannot make them adapt their layout intelligently using the panels described in Chapter 3. If you need these framework-level features, use framework elements as Example 12-17 showed. But if you don’t need to use FrameworkElement-based types in your drawing (maybe because you don’t need the shapes laid out by a panel), the more lightweight DrawingBrush class is more efficient. And, if you are creating lots of drawings, all containing similar shapes, you can even go as far as sharing individual geometry objects as resources. Chapter 13 describes all of these drawing mecha- nisms in more detail. Resources and Styles WPF’s styling mechanism depends on the resource system to locate styles. As you already saw in Chapter 8, styles are defined in the Resources section of an element and can be referred to by name, as Example 12-21 shows. Example 12-21. Referencing a Style resource Example 12-20. Using a Geometry resource (continued) Resources and Styles | 379 Further, it is also possible to define a style that is applied automatically to an ele- ment without the need for the explicit resource reference. This is useful if you want the style to be applied to all elements of a particular type without having to add resource references to every element. Example 12-22 shows a version of Example 12-21 modified to take advantage of this. Notice that the Button no longer has its Style property specified. However, the style will still be applied to the button because of its TargetType. If you were to add more buttons to the window, they would all pick up this style. Instead of defining a key, the style now has a TargetType set with the x:Type markup extension, which instructs XAML to provide the named class’s System.Type object. If a FrameworkElement does not have an explicitly specified Style, it will always look for a Style resource, using its own type as the key. When you create a Style with a TargetType and do not specify the x:Key, the x:Key is implicitly set to be the same as the TargetType. This key is used to locate the style. Data templates use a similar mechanism. In general, you should avoid setting the x:Key to a Type object unless the resource is a Style or DataTemplate that you want to be applied automatically. Because elements look for their styles in resources, you can take advantage of the resource scoping system. You can define a style resource at a local scope if you wish to affect just a small number of elements, or at a broader scope such as in Example 12-22. Implicit use of a Style Example 12-21. Referencing a Style resource (continued) 380 | Chapter 12: Resources Window.Resources, or at the application scope. If your application doesn’t define a style for a particular element, its styles will be retrieved from the system scope. This relationship between styling and resources is the key to both theming and skinning. Skins and Themes Skinning and theming are both techniques for controlling the look and feel of a UI. A theme* is a system-wide look, such as the Classic Windows 2000 look, the Luna theme in Windows XP, and the Aero theme in Windows Vista. A skin is a look spe- cific to a particular application, such as the distinctive styles available with media programs like WinAmp and Windows Media Player. In WPF, skins and themes are both defined as sets of resources that apply the required styles to controls. By setting each resource key to the Type for the control to which the style applies, styles will apply themselves consistently and automatically. These styles will usually set the Template property in order to define the appearance of the control, and may also set other properties such as those for font handling. (Templates were discussed in Chapter 9.) The main difference between a skin and a theme is one of scope—a skin would typically be stored in the application’s Resources property, whereas a theme lives at the system scope, and is not directly associated with any one application. There is currently no documented way of defining a new theme. All you can do is add features to the built-in themes. As we saw in the “Resource Scope” section, earlier in this chapter, you can provide sets of theme-specific resources that will be added to the system scope. Because a skin’s purpose is to control the appearance of a particular application, it may well provide more than just styles for standard controls. It might define various other named resources for use in specific parts of the application. For example, a music player application might present a ListBox whose purpose is to present a list of songs. A skin might well want to provide a particular look for this list without necessarily affecting all listboxes in the application. So the application would probably set that ListBox to use a specific named style, enabling the skin to define a style just for that ListBox. A skin doesn’t necessarily have to provide a comprehensive set of styles. If the appli- cation doesn’t use every single WPF control type, the skin needs to supply styles only for the controls the application uses. Example 12-23 shows the XAML for an extremely simple skin. * Strictly speaking, the proper name is Visual Style. According to official terminology, a theme can include other features, such as system sounds and mouse cursors, as well as the visual style. However, this distinction is rarely observed in practice, so we’ll stick with the shorter name. Resources and Styles | 381 This sets the foreground and background for a Button. It also defines a brush—skins often define graphical resources such as brushes or drawings, as it sometimes takes more than just customizing controls to achieve a harmonious look for your applica- tion. A more complex skin would target more element types and set more proper- ties. Most skins include some Template property setters in order to customize the appearance of controls. But even in this simple example, the underlying principles remain the same. Example 12-24 shows a UI, and Example 12-25 shows the corre- sponding code-behind file that allows skins to be switched. (This example assumes that two skins, BlueSkin and GreenSkin, have been defined* using the technique shown in Example 12-23.) Example 12-23. BlueSkin.xaml—a very simple skin * I haven’t shown the GreenSkin.xaml file, as it’s identical to BlueSkin.xaml, except the word Green replaces the word Blue. Example 12-24. Window1.xaml—switching skins 382 | Chapter 12: Resources Example 12-25. Window1.xaml.cs—code-behind file for switching skins using System; using System.Collections.ObjectModel; using System.Diagnostics; using System.Windows; using System.Windows.Controls; namespace SimpleSkin { public partial class Window1 : Window { public Window1( ) { InitializeComponent( ); EnsureSkins( ); chooseGreenSkin.Click += SkinChanged; chooseBlueSkin.Click += SkinChanged; } static ResourceDictionary greenSkin; static ResourceDictionary blueSkin; void EnsureSkins( ) { // This method is called each time a new Window1 is constructed, // so make sure we only load the resources the first time if (greenSkin !- null) { greenSkin = new ResourceDictionary(); greenSkin.Source = new Uri("GreenSkin.xaml", UriKind.Relative); blueSkin = new ResourceDictionary(); blueSkin.Source = new Uri("BlueSkin.xaml", UriKind.Relative); } } void SkinChanged(object o, EventArgs e) { if (chooseGreenSkin.IsChecked.Value) { ApplySkin(greenSkin); } else { ApplySkin(blueSkin); } } void ApplySkin(ResourceDictionary newSkin) { Collection appMergedDictionaries = Application.Current.Resources.MergedDictionaries; // Remove the old skins (MergedDictionary.Clear won't do the trick) if (appMergedDictionaries.Count != 0) { appMergedDictionaries.Remove(appMergedDictionaries [0]); } Binary Resources | 383 This class contains some code to ensure that the skins get created just once. The code that changes skins over simply ensures that the application resource dictionary’s MergedDictionaries collection contains the ResourceDictionary for the selected skin. The styling and resource systems react automatically to the change in resources, updating all of the affected controls and all DynamicResource references when you switch skins, so this is all the code that is required. Figure 12-5 shows the code in action. Binary Resources Although ResourceDictionary and the resource scope system are fine for data that can easily be contained in an object, not all resources fit comfortably into this model. Often we need to deal with binary streams. For example, images, audio, and video have effi- cient binary representations, but they are not particularly at home in markup, and in the world of objects they are usually represented by wrappers for the underlying data. Markup itself also presents a challenge: XAML pages must somehow get built into our applications. So a means of dealing with binary streams is needed. WPF does not introduce any new technology for dealing with binary data. The .NET Framework has always provided mechanisms for dealing with embedded binary streams, and WPF simply uses these. The lowest level of stream support lets you embed resource streams into any assem- bly. This is a simple matter of supplying the files you would like to embed to the compiler. In Visual Studio, you do this by setting a file’s Build Action property to Embedded Resource. This copies the contents of the file into the assembly as an embedded stream. The stream can be retrieved at runtime using the Assembly class’s GetManifestResourceStream method, as Example 12-26 shows. // Add the new skin appMergedDictionaries.Add(newSkin); } } } Figure 12-5. Changing skins (Color Plate 21) Example 12-25. Window1.xaml.cs—code-behind file for switching skins (continued) 384 | Chapter 12: Resources Streams embedded in this way are called assembly manifest resources. Although WPF ultimately depends on this embedded resource mechanism, it uses it indirectly through the ResourceManager class in the System.Resources namespace. The resource manager builds on the embedded resource system, adding two features: localization, and the ability to store multiple named streams in a single low-level stream. The ResourceManager API allows you to ask for resources by name, and it will attempt to locate the most appropriate resource based on the UI culture. The “Global Applica- tions” section, later in this chapter, describes this in more detail. By convention, a WPF application or component puts all of its resources into a sin- gle assembly manifest resource stream called Appname.g.resources, where Appname is the name of the component or executable without the file extension. We can learn how WPF uses this resource stream by examining it using a ResourceManager. (In a real application, you use a WPF-supplied wrapper for the ResourceManager that we’ll look at shortly. We’re just using ResourceManager to look under the hood.) Example 12-27 shows how to retrieve a list of resource names. Let’s use this to look at the resources found inside a typical application. Figure 12-6 shows the Visual Studio Solution Explorer view for a simple WPF project. It con- tains the usual App.xaml file defining the application, and a single Window1.xaml file defining the user interface. This application also has an Images directory, which contains two bitmap files. As you can see from the Properties panel in the bottom half of Figure 12-6, the Build Action of Sunset.jpg has been set to Resource.* Example 12-26. Retrieving assembly manifest resources Assembly asm = Assembly.GetExecutingAssembly( ); Stream s = asm.GetManifestResourceStream("StreamName"); Example 12-27. Listing binary resources static List GetResourceNames(Assembly asm, System.Globalization.CultureInfo culture) { string resourceName = asm.GetName( ).Name + ".g"; ResourceManager rm = new ResourceManager(resourceName, asm); ResourceSet resourceSet = rm.GetResourceSet(culture, true, true); List resources = new List( ); foreach (DictionaryEntry resource in resourceSet) { resources.Add((string) resource.Key); } rm.ReleaseAllResources( ); return resources; } * This has a different effect than the Embedded Resource action we saw earlier. Embedded Resource embeds the file in its own distinct assembly manifest resource. Resource embeds the file inside the Appname.g.resources assembly manifest resource that is shared by all the files with a build action of Resource. Binary Resources | 385 When you add a bitmap file to a project using Add ➝ New Item or Add ➝ Existing Item from the context menu in the Solution Explorer, its Build Action will be set to Resource automatically, because this is the simplest way to work with binary resources in WPF. Wheel.jpg has the same setting. If we were to call the GetResourceNames function in Example 12-27, and print out each string it returns, we would see the following output: window1.baml images/wheel.jpg images/sunset.jpg As you can see, both of the bitmaps are present. You can use these embedded bit- maps from any element with a property of type ImageSource, as Example 12-28 shows. Figure 12-6. An application with resources Example 12-28. Using a bitmap resource 386 | Chapter 12: Resources Using a relative URL such as this one indicates that the resource is local—relative URLs can be used either when the bitmap file is in the same directory, or when it is compiled in as a resource. Because the bitmap data is embedded inside the resource stream in the application binary, there is no need to ship a separate file containing bitmap data. The resource list also shows a window1.baml resource. This corresponds to the Window1.xaml file. BAML is a binary representation of a XAML file—XAML is compiled into BAML during the compilation process. BAML is significantly more compact than XAML, so your executables are much smaller than they would be if XAML were built in. In a WPF project, any file with a Build Action of Page is assumed to be XAML. It will be compiled into a BAML resource. Although it’s easy to load a resource with the Image element’s Source property, or any property of type ImageSource, what if we want to use a resource from code? We shouldn’t use the ResourceManager directly in a real application, because we would be depending on an implementation detail of WPF’s resource handling. Instead, we should use the wrapper functionality provided by the Application class, because it’s significantly simpler, as well as being the official documented mechanism. Binary Resources and the Application Class The Application class provides four helper functions for retrieving resources: GetResourceStream, GetContentStream, GetRemoteResource, and LoadComponent. GetResourceStream is the proper way to retrieve resources compiled into the execut- able. This wraps the ResourceManager behavior described earlier. You simply need to pass in the URI of the resource, as shown in Example 12-29. The method returns a StreamResourceInfo. This has two properties: Stream contains the resource stream, and ContentType is a string containing the MIME type of the stream. GetContentStream is almost identical to GetResourceStream, as Example 12-30 shows. The only difference is that it retrieves streams stored in files in the same directory on disk as the executable. Example 12-29. Using GetResourceStream Uri resourcePath = new Uri("Images/Sunset.jpg", UriKind.Relative); StreamResourceInfo ri = Application.GetResourceStream(resourcePath); Stream data = ri.Stream; Example 12-30. Using GetContentStream Uri resourcePath = new Uri("Images/Sunset.jpg", UriKind.Relative); StreamResourceInfo ri = Application.GetContentStream(resourcePath); Stream data = ri.Stream; Binary Resources | 387 GetContentStream won’t open just any old stream that happens to be on the disk. The application is required to declare upfront which streams it expects to find. For each content stream you want to load, your executable must contain an assembly-level AssemblyAssociatedContentFile custom attribute specifying the filename. If you set a file’s Build Action to Content, Visual Studio adds this attribute automatically when it builds the file. GetRemoteResource looks just like the previous two methods. Again, the only differ- ence is where it expects the resource to be. This method is intended for applications deployed to a web server (e.g., an XBAP; XBAPs are described in Chapter 11). The method can download files stored on the same web server from which the applica- tion itself came. You would use this method if your application has large resource files that would take a long time to download, not all of which are necessarily needed upfront. By separating the resources out into separate downloads, you can improve the initial startup time of your application. The LoadComponent method is the odd one out here—this does more than simply retrieve a stream. It expects the stream to contain BAML—compiled XAML. It will parse the stream, and generate the tree of objects described by the XAML. The return value is the root of this tree. Example 12-31 uses this to load a resource dictionary. The LoadComponent method is aware of code behind. If the XAML you load has a cor- responding code-behind class, it will create an instance of that. Otherwise, the object returned will be of the type specified by the root element of the XAML file. LoadComponent has an overload that takes two parameters: an object and a URI. This loads and parses the XAML as before, but does not create the root object for you. Instead, you create the root object and pass this in to LoadComponent, which will then load all of the remaining content into the root you supply. This is how XAML nor- mally gets loaded. The generated partial class that Visual Studio creates for a XAML file with code behind uses LoadComponent to populate your object with the objects described by the XAML, as this excerpt in Example 12-32 shows. As we saw earlier, XAML gets compiled into binary files with a .baml extension. It’s therefore slightly surprising to see a .xaml extension in these last two examples. Example 12-31. Application.LoadComponent Uri resourcePath = new Uri("MyResources.xaml"); ResourceDictionary rd = (ResourceDictionary) Application.LoadComponent(resourcePath); Example 12-32. Use of LoadComponent in generated code System.Uri resourceLocater = new System.Uri("/BinaryResources;component/window1.xaml", System.UriKind.Relative); System.Windows.Application.LoadComponent(this, resourceLocater); 388 | Chapter 12: Resources We use a .xaml extension because BAML is essentially an implementation detail. We always refer to resources by their original names, and we don’t need to con- cern ourselves with the exact runtime representation. This is a good reason to use the methods provided by the Application class instead of going straight to ResourceManager. The URI in Example 12-32 is a little more complex than the ones in the previous examples. WPF accepts several different forms of URI for resources. They are all variations around the pack URI scheme. Pack URIs Microsoft has defined a new URI scheme—the pack scheme. URIs that use this scheme are called pack URIs. This URI scheme is part of the Open Packaging Con- ventions, which are the basis of the Office 2007 file formats, and also the XPS file for- mat. (The XPS file format is described in Chapter 15.) This URI scheme defines a convention for referring to resources embedded in files. We can use pack URIs to refer to resources in WPF. This is supported by the various resource methods defined by the Application class, and also within XAML files. For example, the Image ele- ment’s Source property is set as a pack URI. The most straightforward form of pack URI is a relative pack URI—all the examples we’ve looked at so far have been of this form. With a relative pack URI, we can spec- ify just the name of the embedded resource. Example 12-31 used this form. Example 12-32 illustrates a slightly more complex form of relative pack URI. It incor- porates the name of the component that contains the resource. This makes it possi- ble to refer to other components that are loaded by the application—you simply start the URI with /ComponentName;component. The URI in Example 12-32 explicitly refers to a resource defined by a component named BinaryResources. Example 12-33 uses this same technique to refer to a system component (your code should appear on one line; here it’s been split across two lines due to space constraints). This example grabs the resource dictionary containing the theme resources for the default Windows XP Luna color scheme. The controls inside this StackPanel will all have the Luna look regardless of which theme the user has selected. Example 12-33. Relative pack URI referring to external component And of course you can put graphics into your text: Graphics Fundamentals | 397 Not only can graphics and the other content live side by side in the markup, but they can even be intermingled. Notice how in Figure 13-1 the ellipse on the righthand side has been arranged within the flow of the containing TextBlock. If you want to achieve this sort of effect in Windows Forms, it is not possible with its Label con- trol—you would have to write a whole new control from scratch that draws both the text and the ellipse. This mixing goes both ways—not only can you mix controls into your graphics, but you can also use graphical elements inside controls. For example, Figure 13-2 shows a button with mixed text and graphics as its caption. Traditionally in Windows, you would get this effect by relying on the button’s abil- ity to display a bitmap. But bitmaps are just a block of fixed graphics—you can’t eas- ily make parts of a bitmap interactive, or animate selected pieces in response to user input. So, in WPF putting graphics in buttons works a little differently, as you can see in Example 13-2. Figure 13-1. Mixed content Figure 13-2. Button with graphical content Example 13-2. Adding graphics to a Button 398 | Chapter 13: Graphics Of course, buttons with images are not a new idea. For example, the Windows Forms Button has an Image property, and in Cocoa, NSButton has a setImage method. But this is pretty inflexible—these controls allow a single caption and a single image to be set. Compare this to Example 13-2, which uses a StackPanel to lay out the inte- rior of the button and just adds the content it requires. You can use any layout panel inside the Button, with any kind of content. Example 13-3 uses a Grid to arrange text and some ellipses within a Button. Figure 13-3 shows the results. In WPF, there is rarely any need for controls to provide properties, such as Text or Image. If it makes sense for a control to present nested content, it’ll do just that by offering a content model—it will present whatever mixture of elements you choose to provide. If you are familiar with two-dimensional drawing technologies such as Quartz 2D, GDI+, and GDI32, you may have been struck by another difference in the way draw- ing is done. We no longer need to write a function to respond to redraw requests— Example 13-3. Layout within a Button Figure 13-3. Button with Grid content Graphics Fundamentals | 399 WPF can keep the screen repainted for us. This is because WPF lets us represent drawings as objects. Drawing Object Model With many GUI technologies, applications that want customized visuals are required to be able to re-create their appearance from scratch. The usual technique for show- ing a custom appearance is to write code that performs a series of drawing opera- tions in order to construct the display. This code runs when the relevant graphics first need to be displayed. In some systems, the OS does not retain a copy of what the application draws, so this method ends up running anytime an area needs repainting—for example, if a window was obscured and then uncovered. Updating individual elements is often problematicin systems that use this on- demand rendering style. Even where the OS does retain a copy of the drawing, it is often retained as a bitmap. This means that if you want to change one part of the drawing, you often need to repaint everything in the area that has changed. WPF offers a different approach: you can add objects representing graphical shapes to the tree of user interface elements. Shape elements are objects in the UI tree like any other, so your code can modify them at any time. If you change some property that has a visual impact—such as the size, location, or color—WPF will automati- cally update the display. To illustrate this technique, Example 13-4 shows a simple window containing sev- eral ellipses. Each is represented by an Ellipse object, which we will use from the code-behind file to update the display. Example 13-4. Changing graphical elements 400 | Chapter 13: Graphics Example 13-5 shows the code-behind file for this window. It attaches a handler to the main canvas’s MouseLeftButtonDown event. Thanks to event bubbling, this OnClick handler method will be called whenever any of the ellipses is clicked. This method simply increases the Width property of whichever Ellipse raised the event. The result is that clicking on any ellipse will make it wider. If we were using the old approach of drawing everything in a single rendering func- tion, this code would not be sufficient to update the display. It would normally be necessary to tell the OS that the screen is no longer valid, causing it to raise a repaint request. But in WPF, this is not necessary—when you set a property on an Ellipse object, it ensures that the screen is updated appropriately. Moreover, WPF is aware that the items overlap, as shown in Figure 13-4, so it will also redraw the items beneath and above as necessary to get the right results. All you have to do is adjust the properties of the object. Example 13-5. Changing a shape at runtime using System.Windows; using System.Windows.Shapes; namespace ChangeItem { public partial class MainWindow : Window { public MainWindow() : base( ) { InitializeComponent( ); mainCanvas.MouseLeftButtonDown += OnClick; } private void OnClick(object sender, RoutedEventArgs e) { Ellipse r = e.Source as Ellipse; if (r != null) { r.Width += 10; } } } } Figure 13-4. Changing overlapping ellipses Graphics Fundamentals | 401 Even though computer memory capacities have increased by orders of magnitude since GUIs first started to appear, in some situations this object model approach for drawing still might be too expensive. In particular, for applications dealing with vast data sets such as maps, having a complete set of objects in the UI tree mirroring the structure of the underlying data could use too much memory. Also, for certain kinds of graphics or data, it may be more convenient to use the old style of rendering code. Because of this, WPF also supports some lighter weight modes of opera- tion. The “Visual Layer Programming” section, later in this chapter, describes the on-demand rendering mechanisms. The “DrawingBrush” section, also later in this chapter, describes a third technique that is somewhere in between the two, trading off a little flexibility in exchange for better performance—it offers many of the benefits of a retained model, but without the overhead of a full WPF framework element. You may have noticed that all of the drawing we’ve done so far has been with shapes and not bitmaps. WPF supports bitmaps, of course, but there is a good reason to use shapes—you can scale and rotate geometric shapes without losing image quality. This ability to perform high-quality transforms is an important feature of drawing in WPF. Resolution Independence Not only have graphics cards improved dramatically since the first GUIs appeared, but so have screens. For a long time, the only mainstream display technology was the CRT (cathode-ray tube). Color CRTs offer fairly low resolution—they struggle to display images with higher definition than about 100 pixels per inch. However, flat panel displays, which now outsell CRTs, can exceed this by a large margin. One of the authors’ laptops has a display with a resolution of 150 pixels per inch. Displays are available with more than 200 pixels per inch. It is technically possible to create even higher pixel densities. However, there is a potential problem with using these screens: either everything ends up being so small that it becomes unusable or, if the OS is able to scale things up, it may only be able to do so imperfectly, introduc- ing blurring or other problems. This is because of a pixel-based development cul- ture—the vast majority of applications measure their user interfaces in pixels. This is not entirely the result of technical limitations. From the very first version of Windows NT, Win32 has made it possible to draw things in a resolution-independent way, because the drawing API—GDI32—allows you to apply transformations to all of your drawings. GDI+, introduced in 2001, offers the same facility. But just because a feature is available doesn’t mean applications will use it—most applications don’t fully exploit this scalability. 402 | Chapter 13: Graphics Unfortunately, the split between graphics and other UI elements in Win32 means that even if an application does exploit the scalability of the drawing APIs, the rest of the UI won’t automatically follow. Figure 13-5 shows a Windows Forms application that uses GDI+ to draw text and graphics scaled to an arbitrary size. Notice in Figure 13-5 that although the star and the “Hello, world!” text have been scaled, the track bar and label controls have not. This is because drawing transforma- tions affect only what you draw with GDI+—they do not affect the entire UI. And although Windows Forms offers some features to help with scaling the rest of the UI, it’s not completely automatic; you have to take deliberate and nontrivial steps to build a resolution-independent UI in Windows Forms. Scaling and rotation WPF solves this problem by supporting transformations at a fundamental level. Instead of providing scalability just at the 2D drawing level, it is built into the under- lying composition engine. The result is that everything in the UI can be transformed, not just the user-drawn graphics. Going back to our smiley face button in Figure 13-2, we can exploit this scalability with a simple addition just after the first line: The LayoutTransform property is available on all user interface elements in WPF, so you can scale the contents of an entire window just as easily as a single button. Many kinds of transformations are available, and we will discuss them in more detail later. For now, we are simply asking to enlarge the button by a factor of three in both x and y dimensions. Figure 13-5. Incomplete UI scaling in Windows Forms Graphics Fundamentals | 403 Figure 13-6 shows the enlarged button. When compared to the original Figure 13-2, it is larger, obviously. More significantly, the details have become crisper. The rounded edges of the button are easier to see than in the small version. The shapes of the letters are much better defined. And, our graphic is clearer. We get this clarity because WPF has rendered the button to look as good as it can at the specified size. Compare this with the examples in Figure 13-7. Figure 13-7 shows what happens if you simply enlarge a bitmap of the original small button. There are several different ways of enlarging bitmaps. The example on the left uses the simplest algorithm, known as nearest neighbor or, sometimes, pixel doubling. To make the image larger, pixels have been repeated. This lends a very square feel to the image. The example on the right uses a more sophisticated interpo- lation algorithm. It has done a better job of keeping rounded edges looking round, and doesn’t suffer from the chunky pixel effect, but it ends up looking very blurred. Clearly, neither of these comes close to Figure 13-6. Resolution, coordinates, and “pixels” This support for scaling graphics means that there is no fixed relationship between the coordinates your application uses and the pixels on-screen. This is true even if you do not use scaling transforms yourself—a transform may be applied automati- cally to your whole application if it is running on a high-DPI display. What are the default units of measurement in a WPF application if not physical pix- els? The answer is, somewhat confusingly, pixels! To be more precise, the real answer is device-independent pixels. WPF defines a device-independent pixel as 1/96th of an inch. If you specify the width of a shape as 96 pixels, this means that it should be exactly 1 inch wide. WPF will use as many physical pixels as are required to fill 1 inch. For example, high-resolution lap- top screens typically have a resolution of 150 pixels per inch. So, if you make a shape’s width 96 “pixels,” WPF will render it 150 physical pixels wide. Figure 13-6. Enlarged button with graphics Figure 13-7. Enlarged bitmaps 404 | Chapter 13: Graphics WPF discovers the physical pixel size from the system-wide display settings, so these need to be set accurately in order for elements to be displayed at the correct size. However, very few systems have this con- figured correctly, so the physical dimensions are often arbitrary in practice. But it’s easy enough to configure your system correctly if you know the pixel density. On Windows Vista, you can change this setting by right-clicking on the desktop, selecting Personalize, and then choosing “Adjust font size (DPI)” from the list of options that appears on the left. In the DPI scal- ing window that appears, click the Custom DPI button. Or in Win- dows XP, right-click on your desktop and select Properties to display the applet, and then go to the Settings tab. Click on the Advanced but- ton, and in the dialog that opens, select the General tab. This lets you tell Windows your screen resolution. If you set the number to match the physical characteristics of your screen, WPF will render content at the correct physical size. You might be wondering why WPF uses the somewhat curious choice of 1/96th of an inch, and why it calls this a “pixel.” The reason is that 96 dpi is the default dis- play DPI in Windows when it is running with Small Fonts, so this has long been con- sidered the “normal” size for a pixel. This means that on screens with a normal pixel density, a device-independent pixel will correspond to a physical pixel. On screens with a high pixel density, if the system DPI is correctly configured, WPF will scale your drawings for you so that they remain at the correct physical size, so a device- independent pixel may not correspond to an exact number of physical pixels. WPF’s capability to optimize its rendering of graphical features for any scale means it is ideally placed to take advantage of increasing screen resolutions. For the first time, on-screen text and graphics will be able to compete with the crisp clarity we have come to expect from laser printers. Of course, for all of this to work in practice, we need a comprehensive suite of scalable drawing primitives. Shapes, Brushes, and Pens Most of the classes in WPF’s drawing toolkit fall into one of three categories: shapes, brushes, and pens. There are many variations on these themes, and we will examine them in detail later. However, to get anywhere at all with graphics, we need a basic understanding. Shapes are objects in the user interface tree that provide the basic building blocks for drawing. The Ellipse, Path, and Rectangle elements we have seen already are all examples of shape objects. There is also support for lines, both single- and multi- segment, using Line and Polyline, respectively. Polygon creates closed shapes whose edges are all straight. The Path class supports both open and closed shapes with any mixture of straight and curved edges. Figure 13-8 shows each of these shapes in action. Graphics Fundamentals | 405 Regardless of which shape you choose, you’ll need to decide how it should be col- ored in. For this, you use a brush. Many brush types are available. The simplest is the single-color SolidColorBrush. You can achieve more interesting visual effects using the LinearGradientBrush or RadialGradientBrush. These allow the color to change over the surface of a shape, which can be a great way of providing an impression of depth. You can also create brushes based on images—the ImageBrush uses a bitmap, and the DrawingBrush uses a scalable drawing. Finally, the VisualBrush lets you take any visual tree—any chunk of user interface you like—and use that as a brush to paint some other shape. This makes it easy to achieve effects such as reflections of whole sec- tions of your user interface, or wrapping a user interface around a 3D model. Finally, pens are used to draw the outline of a shape. A pen is really just an aug- mented brush. When you create a Pen object, you give it a Brush to tell it how it should paint onto the screen. The Pen class just adds information like line thickness, dash patterns, and end cap details. Figure 13-9 shows a few of the effects available using brushes and pens. Composition The final key feature of the graphics architecture is composition. In computer graph- ics, the term composition refers to the process of combining multiple shapes or images together to form the final output. WPF’s composition model is very different from how Windows has traditionally worked, and it is crucial to enabling the cre- ation of high-quality visuals. In the classic Win32 model, each user interface element (each HWND) has exclusive ownership of some region of the application’s window. Within each top-level win- dow, any given pixel in that window is controlled completely by exactly one ele- ment. This prevents elements from being partially transparent. It also precludes the use of anti-aliasing around the edges of elements, a technique which is particularly important when combining nonrectangular elements. Although various hacks have Figure 13-8. Rectangle, Ellipse, Line, Polyline, Polygon, and Path Figure 13-9. Brushes and pens 406 | Chapter 13: Graphics been devised to provide the illusion of transparency in Win32, they all have limita- tions, and can be somewhat inconvenient to work with. WPF’s composition model supports elements of any shape, and allows them to overlap. It also allows elements to have any mixture of partially and completely transparent areas. This means that any given pixel on-screen may have multiple con- tributing visible elements. Moreover, WPF uses anti-aliasing around the edges of all shapes. This reduces the jagged appearance that simpler drawing techniques can pro- duce on-screen, resulting in a smooth-looking image. Finally, the composition engine allows any element to have a transformation applied before composition. WPF’s composition engine makes use of the capabilities of modern graphics cards to accelerate the drawing process. Internally, it is implemented on top of Direct3D. This may seem odd because the majority of WPF’s drawing functionality is two- dimensional, but most of the 3D-oriented functionality on a modern graphics card can also be used to draw 2D shapes. For example, WPF exploits the same ultra-fast polygon-drawing facilities used by 3D games to render primitive shapes. Now that we’ve seen the core concepts underpinning the WPF graphics system, let’s take a closer look at the details. Shapes The System.Windows.Shapes namespace defines drawing primitives that act as ele- ments in the user interface tree. WPF supports a variety of different shapes, and pro- vides element types for each of them, which are shown in Table 13-1. These integrate with framework-level functionality such as layout, styling, and data binding. These services are not without their costs, so it’s useful to be aware that the shape classes provide a layer of abstraction on top of a lower-level set of services. See the “Shape Objects Versus Geometries” sidebar for details. Table 13-1. Shapes Shape Type Usage Ellipse An ellipse Line A single straight line Path A shape using any mixture of straight lines and curves Polygon A closed shape made from straight lines Polyline An open shape made from straight lines Rectangle A rectangle, optionally with rounded corners Shapes | 407 Base Shape Class All of the elements described in this section derive from a common abstract base class, Shape. Shape defines a common set of features that you can use on all shapes. These common properties are mainly concerned with the way in which the interior and outline of the shape are painted. The Fill property specifies the Brush that will be used to paint the interior. (The Line class doesn’t have an interior, so it ignores this property. This was simpler than com- plicating the inheritance hierarchy by having separate Shape and FilledShape base classes.) The Stroke property specifies the Brush that will be used to paint the out- line of the shape. If you do not specify either a Fill or a Stroke for your shape, it will be invisible, because both of these properties are null by default. Shape Objects Versus Geometries A common source of confusion for people learning WPF is that it appears to have two sets of graphical classes. For example, as well as Rectangle, there is RectangleGeometry, and we have Path as well as PathGeometry. Most of the shape classes in the System.Windows.Shapes namespace have corresponding geometry classes in the System.Windows.Media namespace. Although this may seem redundant, the two kinds of classes serve different purposes. Geometries are just descriptions of shapes. For example, a LineGeometry defines a StartPoint and an EndPoint.ALineGeometry does not know what color it should be. It cannot raise mouse or stylus events. It cannot interact with the layout system. It has no concept of where it is in the UI tree or even which window it is in. (Indeed, geometries can be shared simultaneously and efficiently by many different windows.) The classes in the System.Windows.Shapes namespace provide a way to host geometries in the UI tree. They provide brushes and pens with which to paint the geometries. They are able to adjust geometries to adapt to layout changes. These shape types provide a route through which animations and data binding expressions can target geometries. Shape objects are not the only way of using geometries. Indeed, they are a relatively expensive way to do so. Geometries are also the basis of drawings and of visual layer programming, which provide less convenient but more efficient ways of displaying graphics. The choice of which to use will typically be driven by your requirements—if you need layout, data binding, or other framework features for individual shapes, use the classes derived from Shape. If you are building up a picture out of several static shapes, geometries and drawings are likely to be a better choice. 408 | Chapter 13: Graphics It may seem peculiar that the Stroke property is of type Brush. As we saw earlier, WPF defines a Pen class for specifying a line’s thickness, dash patterns, and the like, so it would make more sense if the Stroke property were of type Pen. WPF does in fact use a Pen internally to draw the outline of a shape. The Stroke property is of type Brush mainly for convenience—all of the Pen features are exposed through separate properties on Shape, as shown in Table 13-2. This simplifies the markup in scenarios where you’re happy to use the default pen settings—you don’t need to provide a full Pen definition just to set the outline color. The “Brushes and Pens” section, later in this chapter, describes brushes and pens in detail. The Shape class also defines a Stretch property, which determines how a shape will be adjusted if the available space doesn’t match its preferred size. None means that the shape will simply be whatever size and shape you ask for. If you set this to Fill, the shape will be adjusted to fill the available space. Fill allows the shape to be dis- torted if necessary in order to fit exactly. Uniform and UniformToFill scale equally, in both directions. The former scales until the shape is large enough in at least one dimension to fill the available space, but will leave spare space on the other dimen- sion if necessary so as to avoid cropping. You can see this on the lefthand side of Figure 13-10. The latter scales the shape until it’s large enough to completely fill the space in both dimensions, even if this means cropping in one, as shown on the right of Figure 13-10. Table 13-2. Shape Stroke properties and Pen equivalents Shape property Equivalent Pen property Stroke Brush StrokeThickness Thickness StrokeLineJoin LineJoin StrokeMiterLimit MiterLimit StrokeDashArray DashArray StrokeDashCap DashCap StrokeDashOffset DashOffset StrokeStartLineCap StartLineCap StrokeEndLineCap EndLineCap Figure 13-10. Uniform (left) and UniformToFill (right) Shapes | 409 Rectangle and Ellipse default to a Stretch of Fill, whereas the other shapes all default to None. The classes that derive from Shape all add properties specific to the kind of shape they represent. So, we will now look at each of these types, starting with Rectangle. Rectangle Rectangle does what its name suggests. As with any shape, it can be drawn either filled in, as an outline, or both filled in and outlined. As well as drawing a normal rectangle, it can also draw one with rounded corners. Rectangle doesn’t provide any properties for setting its location. It relies on the same layout mechanisms as any other UI element. The location is determined by the con- taining panel. The width and height can either be set automatically by the parent, or they can be set explicitly using the standard layout properties, Width and Height. Example 13-6 shows a Rectangle on a Canvas panel. Here the Width and Height have been set explicitly, and the location has been specified using the attached Canvas.Left and Canvas.Top properties. Example 13-7 shows the other approach; none of the rectangles has its location or size set explicitly. They are relying on the containing Grid to do this. Figure 13-11 shows the result. Example 13-6. Rectangle with explicit size and position Example 13-7. Rectangles with size and position controlled by parent 410 | Chapter 13: Graphics A Rectangle will usually be aligned with the coordinate system of its parent panel. This means that its edges will normally be horizontal and vertical, although if the parent panel has been rotated, Rectangle will of course be rotated along with it. If you want to rotate a Rectangle relative to its containing panel, you can use the RenderTransform property available on all user interface elements, as Example 13-8 shows. Figure 13-11. Rectangles arranged by a Grid Example 13-8. Rotating rectangles Shapes | 411 This uses RenderTransform to rotate a series of rectangles. Figure 13-12 shows the result. To draw a rectangle with rounded corners, use the RadiusX and RadiusY properties, as Example 13-9 illustrates. Figure 13-13 shows the result. Ellipse Ellipse is similar to Rectangle. Obviously it draws an ellipse rather than a rectangle, but the size, location, rotation, fill, and stroke of an Ellipse are controlled in exactly the same way as for a Rectangle, as Example 13-10 shows. Figure 13-14 shows the result. Figure 13-12. Rotated rectangles Example 13-9. Rounded rectangle Figure 13-13. Rectangle with rounded corners Example 13-10. Ellipse Example 13-8. Rotating rectangles (continued) 412 | Chapter 13: Graphics Line The Line element draws a straight line from one point to another. It has four proper- ties controlling the location: X1 and Y1 define the start point, and X2 and Y2 deter- mine the end point. These coordinates are relative to wherever the parent panel chooses to locate the Line. Consider Example 13-11. This uses a vertical StackPanel to arrange an alternating sequence of TextBlock and Line elements. The TextBlock elements have gray backgrounds to make it easier to see the vertical extent of each element (see Figure 13-15). As you can see from Figure 13-15, the Line elements have been placed in the stack just like any other element. The StackPanel has allocated enough height to hold the line. The first of the lines is interesting in that there is some space between the TextBlock above it, and the start of the line. This is because the line’s Y1 property has been set to 10, indicating that the line should start slightly below the top of the loca- tion allocated for the Line element. (In WPF, positive Y means down, unlike with a typical mathematical graph.) The second Line element goes all the way to the top because its Y2 property is set to 0, again illustrating that the coordinate system of the line end points is relative to the area allocated to the Line by the containing panel. You can use the Stretch property to make the Line resize automatically with your layout. The Line in Example 13-12 has start and end points of 0,0 and 1,0. However, because its Stretch is set to Fill, the points will automatically be adjusted to fill the available width. Figure 13-14. Ellipse Example 13-11. Two Line elements in a StackPanel Foo Bar Figure 13-15. Two Line elements in a StackPanel Shapes | 413 Polyline A Polyline lets you draw a connected series of line segments. Instead of having prop- erties for start and end points, Polyline has a Points property, containing a list of coordinate pairs, as Example 13-13 illustrates. WPF simply draws a line that goes through each point in turn, as shown in Figure 13-16. As with the Line class, the point coordinates in a Polyline are relative to wherever the containing panel chooses to locate the Polyline. Polygon Polygon is very similar to Polyline. It has a Points property that works in exactly the same way as Polyline’s. The only difference is that whereas Polyline always draws an open shape, Polygon always draws a closed shape. To illustrate the difference, Example 13-14 contains a Polyline and a Polygon. They have all of the same proper- ties set. As you can see in Figure 13-17, the Polyline has been left open. The Polygon, on the other hand, has closed the shape by drawing an extra line segment between the last and first points. Both shapes have painted interiors. Example 13-12. Auto-sizing line Example 13-13. Polyline Figure 13-16. A Polyline Example 13-14. A Polyline and a Polygon 414 | Chapter 13: Graphics Because we are free to add points wherever we like to a Polygon, it is easy to end up with a self-intersecting shape (one whose edge crosses itself). With such shapes, what counts as the interior of the shape can be ambiguous. Figure 13-18 shows such a shape, and two possible ways of filling it. The Polygon class provides a FillRule property that tells WPF how to deal with ambiguous regions.* WPF supports two fill rules. Example 13-15 is the markup for Figure 13-18, and shows both fill rules in use. The default rule is EvenOdd, and this is used on the left of Figure 13-18. This is the simplest rule to understand. To determine whether a particular enclosed region is inside or outside the shape, the EvenOdd rule counts the number of lines you have to cross to get from that point to one completely outside the shape. If this number is odd, the point was inside the shape. If it is even, the point is outside the shape. For example, if you start from inside the middle area of the star in Figure 13-18, you will need to cross over an even number of lines in order to get to the outside of the shape. This is why the central area of the star is unfilled when the EvenOdd rule is used. The second fill rule, Nonzero, is subtler. From Figure 13-18, you might have thought that any enclosed area was deemed to be inside the shape, but it’s not quite that sim- ple. The Nonzero rule performs a similar process to EvenOdd, but rather than simply counting the number of lines, it takes into account the direction in which the line is running. It either increments or decrements the count for each line it crosses, Figure 13-17. A Polyline (left) and a Polygon (right) Figure 13-18. Fill rules: EvenOdd (left) and Nonzero (right) * In some graphics systems, this is described as the “winding” rule. Example 13-15. Fill rules Shapes | 415 depending on the direction.* If the total at the end is nonzero, the point is consid- ered to be inside the shape. The points making up the stars in Example 13-15 always proceed clockwise. This means that if you start from the center of the star, the two lines you must cross to get to the outside of the shape will always point in the same direction. This results in a count of 2 (or –2, depending on which direction you go), which is why the star on the right of Figure 13-18 has its central region filled. In Figure 13-18, the Nonzero rule has resulted in all enclosed regions being part of the interior. However, if the outline of the shape follows a slightly more convoluted path, the results can be a little more mixed, as Example 13-16 shows. Figure 13-19 shows the results of Example 13-16. This illustrates that the nonzero rule is not quite as straightforward as it may at first seem. The nonzero rule is a bit of an oddity. It was popularized by PostScript, so most drawing systems support it, but it’s not always easy to get useful results from a Polygon with this fill rule. It makes more sense in the context of the Path element, which supports multiple figures in a single shape. Path Path is by far the most powerful shape. All of the shapes we have looked at up to now have been supplied for convenience, because it is possible to draw all of them with a Path. Path also makes it possible to draw considerably more complex shapes than is possible with the previous shapes we have seen. As mentioned earlier, the various classes derived from Shape are essentially high-level wrappers around underlying geometry objects. Path is explicit about this—its shape is defined by its Data property, which is of type Geometry. As we saw in the sidebar earlier, a Geometry object describes a particular shape. Table 13-3 shows the various concrete classes for representing different kinds of shapes. * WPF doesn’t document whether the positive direction is clockwise or counterclockwise. This is because it doesn’t matter—as long as you are consistent, the final outcome is the same either way. Example 13-16. Nonzero fill rule with more complex shape Figure 13-19. Nonzero rule in action 416 | Chapter 13: Graphics Three geometry types—RectangleGeometry, EllipseGeometry, and LineGeometry— correspond to the Rectangle, Ellipse, and Line shape types shown earlier. So this Rectangle: is effectively shorthand for this Path: You might be wondering when you would ever use the RectangleGeometry, EllipseGeometry,orLineGeometry in a Path instead of the simpler Rectangle, Ellipse, and Line. One reason is that Path lets you use a special kind of geometry object called a GeometryGroup to create a shape with multiple geometries. There is a significant difference between using multiple distinct shapes, and having a single shape with multiple geometries. Look at Example 13-17, for instance. This draws two ellipses, one on top of the other. They both have a black outline, so you can see the smaller one inside the larger one, as Figure 13-20 shows. Because the Ellipse shape is just a simple way of creating an EllipseGeometry, the code in Example 13-17 is equivalent to the code in Example 13-18. (As you can see, using a Path is considerably more verbose. This is why the Ellipse and other simple shapes are provided.) Table 13-3. Geometry types Type Usage CombinedGeometry Combines two geometry objects using set operations such as intersection or union EllipseGeometry An ellipse GeometryGroup Combines multiple geometries into one multifigure geometry LineGeometry A single straight line PathGeometry Defines shapes with any combination of straight lines, elliptical arcs, and Bézier curves RectangleGeometry A rectangle StreamGeometry More efficient alternative to PathGeometry—can define all the same shapes, but cannot modify the shapes after creation Example 13-17. Two Ellipse elements Shapes | 417 Because the code in Example 13-18 is equivalent to that in Example 13-17, it results in exactly the same output, as previously shown in Figure 13-20. So far, using geom- etries instead of shapes hasn’t made a difference in the rendered results. This is because we are still using multiple shapes. So we will now show how you can put both ellipses into a single Path, and see how this affects the results. Example 13-19 shows the modified markup. This version has just a single path. Its Data property contains a GeometryGroup. This allows any number of geometry objects to be added to the same path. Here we have added the two EllipseGeometry elements that were previously in two separate paths. The result, shown in Figure 13-21, is clearly different from the one in Figure 13-20— there is now a hole in the middle of the shape. Because the default even-odd fill rule was in play, the smaller ellipse makes a hole in the larger one. (GeometryGroup has a FillRule property that lets you choose the nonzero rule instead if you need to.) Figure 13-20. Two Ellipse elements Example 13-18. Two Paths with EllipseGeometry elements Example 13-19. One Path with two EllipseGeometry elements 418 | Chapter 13: Graphics You can create shapes with holes only by combining multiple figures into a single shape. You could try to get a similar effect to that shown in Figure 13-21 by drawing the inner Ellipse with a Fill color of White, but that trick fails to work as soon as you draw the shape on top of something else, as Figure 13-22 shows. You might be wondering whether you could just draw the inner ellipse using the Transparent color, but that doesn’t work either—if you tried this, you’d still see all of the larger ellipse, rather than what is behind it. Drawing something as totally transparent has the same effect as drawing nothing at all—that’s what transparency means. Only by knocking a hole in the shape can we see through it. To understand why, think about the drawing process. When it ren- ders our elements to the screen, WPF draws the items one after the other. It starts with whatever’s at the back—the text, in this case. Then it draws the shape on top of the text, which effectively obliter- ates the text that was underneath the shape. (It’s still there in the ele- ment tree, of course, so WPF can always redraw it later if you change or remove the shape.) Because you just drew over the text, you can’t draw another shape on top to “undraw” a hole into the first shape. So, if you want a hole in a shape, you’d better make sure that the hole is there before you draw it! This is not to say you’d never use the Transparent color. It has a cou- ple of uses. An animation might fade from a nontransparent color to Transparent in order to make an element disappear gradually. Also, objects that are Transparent are invisible to the eye, but not to the mouse—WPF’s input system (which was described in Chapter 4) treats all brushes as equal, ignoring transparency. So the Transparent color provides a way of making invisible clickable targets. We have not yet looked at the most flexible geometry: PathGeometry. This is the underlying geometry used by Polyline and Polygon, but it can draw many more shapes besides. Figure 13-21. Path with two geometries Figure 13-22. Spot the fake hole Shapes | 419 A PathGeometry contains one or more PathFigure objects, and each PathFigure repre- sents a single open or closed shape in the path. To define the shape of each figure’s outline, you use a sequence of PathSegment objects. Like GeometryGroup, PathGeometry also has a FillRule property to set the behavior for overlapping figures. Again, this defaults to the even-odd rule. PathGeometry’s ability to contain multiple figures overlaps slightly with GeometryGroup’s ability to contain multiple geometries. This is just for convenience—if you need to make a shape where every piece will be a PathGeometry object, it is more compact to have a single PathGeometry with multiple PathFigures. If you just want a group of simpler geome- tries like LineGeometry or RectangleGeometry, it is simpler to use a GeometryGroup and avoid PathGeometry altogether. Example 13-20 shows a simple path. This contains just a single figure in the shape of a square. Figure 13-23 shows the result. This seems like a vast amount of effort for such a sim- ple result—we’ve used 15 lines of markup to achieve what we could have achieved with a single Rectangle element. This is why WPF supplies classes for the simpler shapes and geometries. You don’t strictly need any of them because you can use Path and PathGeometry instead, but the simpler shapes require much less effort. Normally you would use Path only for more complex shapes. Example 13-20. A square Path Figure 13-23. A square Path 420 | Chapter 13: Graphics Even though Example 13-20 produces a very simple result, it illustrates most of the important features of a Path with a PathGeometry. As with all the previous examples, the geometry is in the path’s Data property. The PathGeometry is a collection of PathFigures, so all of the interesting data is inside its Figures property. This example contains just one PathFigure, but you can add as many as you like. The shape of the PathFigure is determined by the items in its Segments property. The starting point of a PathFigure is determined by its StartPoint property. One or more segments describe the figure’s shape. In Example 13-20, these are all LineSegments because the shape has only straight edges, but several types of curves are also on offer. This particular figure is a closed shape, which is determined by the IsClosed property. You might be wondering why LineSegments don’t work like the Line shape or a LineGeometry. With those types, we specify start and end points, as in Example 13-11. This seems simpler than LineSegment, which needs us to specify a StartPoint in the PathFigure. However, line segments in a PathFigure can’t work like that because there cannot be any gaps in the outline of a figure. With the Line ele- ment, each Line is a distinct shape in its own right, but with a PathFigure, each segment is a part of the shape’s outline. To define a figure fully and unambiguously, each segment must start off from where the previous one finished. This is why the LineSegment only spec- ifies an end point for the line. All of the segment types work this way. Example 13-20 isn’t very exciting; it just uses straight line segments. We can create much more interesting shapes by using one of the curved segment types instead. Table 13-4 shows all of the segment types. ArcSegment lets you add elliptical curves to the edge of a shape. ArcSegment is a little more complex to use than a simple LineSegment. As well as specifying the end point of the segment, we must also specify two radii for the ellipse with the Size property. Table 13-4. Segment types Segment type Usage LineSegment Single straight line PolyLineSegment Sequence of straight lines ArcSegment Elliptical arc BezierSegment Cubic Bézier curve QuadraticBezierSegment Quadratic Bézier curve PolyBezierSegment Sequence of cubic Bézier curves PolyQuadraticBezierSegment Sequence of quadratic Bézier curves Shapes | 421 The ellipse size and the line start and end points don’t provide enough information to define the curve unambiguously, because there are several ways to draw an ellipti- cal arc given these constraints. Consider a segment with a particular start and end point, and a given size and orientation of ellipse. For this segment, there will usually be two ways in which we can position the ellipse so that both the start and end points lie on the boundary of the ellipse, as Figure 13-24 shows. In other words, there will be two ways of “slicing” an ellipse with a particular line. For each way of slicing the ellipse, there will be two resulting arc segments, a small one and a large one. This means that there are four ways in which the curve could be drawn between two points. The ArcSegment provides two flags that enable you to select which of the curves you require. IsLargeArc determines whether you get the larger or smaller slice size. SweepDirection chooses on which side of the line the slice is drawn. Example 13-21 shows markup for all four combinations of these flags. It also shows the whole ellipse. Figure 13-24. Potential ellipse positions Example 13-21. ArcSegments 422 | Chapter 13: Graphics You may be wondering why the Ellipse has a width of 140 and a height of 60, which is double the Size of each ArcSegment. This is because the ArcSegment interprets the Size as the two radii of the ellipse, whereas the Width and Height properties on the Ellipse indi- cate the total size. Figure 13-25 shows the results, and as you can see, each shape has one straight diag- onal line and one elliptical curve. The straight line edge has the same length and ori- entation in all four cases. The curved edge is from different parts of the same ellipse. In Figure 13-25, the ellipse’s axes are horizontal and vertical. Sometimes you will want to use an ellipse where the axes are not aligned with your main drawing axes. ArcSegment provides a RotationAngle property, allowing you to specify the amount of rotation required in degrees. Figure 13-26 shows four elliptical arcs. These use the same start and end points as Figure 13-25, and the same ellipse size. The only difference is that a RotationAngle of 45 degrees has been specified, rotating the ellipse before slicing it. There are two degenerate cases in which there will not be two ways of slicing the ellipse. The first is when the slice cuts the ellipse exactly in half. In this case, the IsLargeArc flag is irrelevant, because both slices are exactly the same size. The other case is when the ellipse is too small—if the widest point at which the ellipse could be sliced is narrower than the segment is long, there is no way in which the segment can be drawn correctly. (If you do make the ellipse too small, WPF seems to scale the ellipse so that it is large enough, preserving the aspect ratio between the x- and y-axes.) You should avoid this. Figure 13-25. An ellipse and four arcs from that ellipse Figure 13-26. Four arcs from a rotated ellipse Shapes | 423 The remaining curve types (BezierSegment, PolyBezierSegment, QuadraticBezierSegment, and PolyQuadraticBezierSegment) are variations on the same theme. They all draw Béz- ier curves. Bézier curves Bézier curves are curved line segments joining two points using a particular mathe- matical formula. It is not necessary to understand the details of the formula in order to use Bézier curves. What makes Bézier curves useful is that they offer a fair amount of flexibility in the shape of the curve. This has made them very popular—most vec- tor drawing programs offer them.* Figure 13-27 shows a variety of Bézier curve segments. Each of the five lines shown here is a single BezierSegment. As with all of the segment types, a BezierSegment starts from where the preceding segment left off, and defines a new end point. It also requires two “control points” to be defined, and it is these that determine the shape of the curve. Figure 13-28 shows the same curves again, but with the control points drawn on. It also shows lines con- necting the control points to the segment end points, because this makes it easier to see how the control points affect the curve shapes. The most obvious way in which the control points influence the shapes of these curves is that they determine the tangent. At the start and end of each segment, the direction in which the curve runs at that point is exactly the same as the direction of the line joining the start point to the corresponding control point. There is a second, less obvious way in which control points work. The distance between the start or end point and its corresponding control point (i.e., the length of * If you’d like to understand the formula for Bézier curves, http://mathworld.wolfram.com/BezierCurve.html (http://tinysells.com/69) and http://en.wikipedia.org/wiki/B%C3%A9zier_curve (http://tinysells.com/70) both provide good descriptions. Figure 13-27. Bézier curve segments Figure 13-28. Bézier curves with control points shown 424 | Chapter 13: Graphics the straight lines added on Figure 13-28) also has an effect. This essentially deter- mines how extreme the curvature is. Figure 13-29 shows a set of Bézier curves similar to those in Figure 13-28. The tan- gents of both ends of the lines remain the same, but in each case, the distance between the start point and the first control point is reduced to one-quarter of what it was before, whereas the other is the same as before. As you can see, this reduces the influence of the first control point. In all four cases, the shape of the curve is dominated by the control point that is farther from its end point. Example 13-22 shows the markup for the second curve segment in Figure 13-28. The Point1 property determines the location of the first control point—the one associ- ated with the start point. Point2 positions the second control point. Point3 is the end point. (To keep things clear, the examples in this section just show the relevant PathFigure elements. If you want to see these shapes, you would of course need to put them inside a PathGeometry inside a Path, just as with the previous examples.) Flexible though Bézier curves are, you will rarely use just a single one. When defin- ing shapes with curved edges, it is normal for a shape to have many Bézier curves defining its edge. WPF therefore supplies a PolyBezierSegment type, which allows multiple curves to be represented in a single segment. It defines a single Points prop- erty, which is an array of Point structures. Each Bézier curve requires three entries in this array: two control points and an end point. (As always, each segment starts from where the previous one left off.) Example 13-23 shows an example segment with two curves. Figure 13-30 shows the results. Figure 13-29. Bézier curves with less extreme control points Example 13-22. BezierSegment Example 13-23. PolyBézierSegment Shapes | 425 This markup is less convenient than simply using a sequence of BezierSegment ele- ments, which rather defeats the point. Fortunately, you can provide all of the point data in string form. This is equivalent to Example 13-23: Also, if you are generating coordinates from code, dealing with a single PolyBezierSegment and passing it an array of Point data is often easier than working with lots of individual segments. Cubic Bézier curves provide a lot of control over the shape of the line. However, you might not always need that level of flexibility. The QuadraticBezierSegment uses a simpler equation that uses just one control point to define the shape of the curve. This does not offer the same range of curve shapes as a cubic Bézier curve, but if all you want is a simple shape, this reduces the number of coordinate pairs you need to provide by one-third. QuadraticBezierSegment is similar in use to the normal BezierSegment. The only dif- ference is that it has no Point3 property—just Point1 and Point2. Point1 is the single control point, and Point2 is the end point. PolyQuadraticBezierSegment is the multi- curve equivalent. You use this in exactly the same way as PolyBezierSegment, except you need to provide only two points for each segment. Combining shapes Geometries can perform one more trick that we have not yet examined. We can com- bine geometries to form new geometries. This is different from adding two geome- tries to a GeometryGroup—it is possible to combine pairs of geometries in a way that forms a single geometry with a whole new shape. Examples 13-24 and 13-25 define paths, both of which make use of the same RectangleGeometry and EllipseGeometry. The difference is that Example 13-24 puts both into a GeometryGroup, while Example 13-25 puts them into a CombinedGeometry. Figure 13-30. PolyBezierSegment Example 13-23. PolyBézierSegment (continued) 426 | Chapter 13: Graphics Figure 13-31 shows the results of Examples 13-24 and 13-25. Whereas the GeometryGroup has resulted in a shape with multiple figures (taking the default fill rule into account), the CombinedGeometry has produced a single figure. The ellipse geometry has taken a bite out of the rectangle geometry. This is just one of the ways in which geometries can be combined. The GeometryCombineMode property deter- mines which is used, and Figure 13-32 shows all four available modes. Example 13-24. Multiple geometries Example 13-25. Combined geometries Figure 13-31. Grouping and combining geometries Figure 13-32. Combine modes: Union, Intersect, Xor, and Exclude Union Intersect Xor Exclude Shapes | 427 Union builds a shape in which any point that was inside either of the two original shapes will also be inside the new shape. Intersect creates a shape where only points that were inside both shapes will be in the new shape. Xor creates a shape where points that were in one shape or the other, but not both, will be in the new shape. Exclude creates a shape where points inside the first shape but not inside the second will be included. Path geometry text format We have now looked at all of the features that Path has to offer. As you have seen, we can end up with some pretty verbose markup. Fortunately, there is a shorthand mechanism that allows us to exploit most of the features we have seen without hav- ing to type quite so much. So far, we have been setting the Data property using XAML’s property element syn- tax. (See Appendix A for more details on this syntax.) However, we can supply a string instead. Example 13-26 shows both techniques. As you can see, the string form is some 12 lines shorter. The syntax for the text form of the Path.Data property is simple. The string must contain a sequence of commands. A command is a letter followed by some numeric parameters. The number of parameters required is determined by the chosen com- mand. Lines require just a coordinate pair. Curves require more data. Example 13-26. Path.Data as text 428 | Chapter 13: Graphics If you omit the letter, the same command will be used as last time. For instance, Example 13-26 uses the L command—this is short for Line, and it represents a LineSegment. This requires only two numbers: the coordinates of the line end point. And yet, in our example, there are six numbers. This simply indicates that there are three lines in a row. Table 13-5 lists the commands, their equivalent segment types where applicable, and their usage. The commands M, Z, F0, and F1 do not correspond to segments. The M command causes a new PathFigure to be started, enabling multiple figures to be represented in this compact text format. Z sets the current figure’s IsClosed property to true. F0 and F1 set the FillRule of the PathGeometry. Notice that there are two ways to specify a BezierSegment. The C command lets you provide all of the control points. The S command generates the first control point for you—it looks at the preceding segment and makes the first control point a mir- ror image of the preceding one. This ensures that the segment’s tangent aligns with the preceding segment’s tangent, resulting in a smooth join between the lines. Table 13-5. Path.Data commands Command Command name Segment type Parameters M (or m) Move Coordinate pair: the StartPoint for a new PathFigure L (or l) Line LineSegment Coordinate pair: end point H (or h) Horizontal line LineSegment Single coordinate: end x coordinate (y coordinate will be the same as before) V (or v) Vertical line LineSegment Single coordinate: end y coordinate (x coordinate will be the same as before) C (or c) Cubic Bézier curve BezierSegment Three coordinate pairs: two control points and one end point Q (or q) Quadratic Bézier curve QuadraticBezierSegment Two coordinatepairs:control pointand end point S (or s) Smooth Bézier curve BezierSegment Two coordinate pairs: second control point and end point (first control point generated automatically) T (or t) Smooth quadratic Bézier curve QuadraticBezierSegment One coordinate pair: end point (control point generated automatically) A (or a) Elliptical arc ArcSegment Seven numbers: x radius, y radius, RotationAngle, IsLargeArc, SweepDirection, and end point coordinate pair Z (or z) Close path None F0 Even-odd fill rule None F1 Nonzero fill rule None Bitmaps | 429 QuadraticBézier segments have a similar facility:the Q command lets you specify the control point, whereas the T command generates the control point for you in a way that guarantees a smooth line. You can specify any of these commands in either uppercase or lowercase. In the uppercase form, coordinates are relative to the position of the Path element. If the command is lowercase, the coordinates are taken to be relative to the end point of the preceding segment in the path. As well as being offered for the Path.Data property, this path syntax can also be used directly with a PathGeometry—its Figures property supports the same syntax. Another geometry type also supports this mini path language: StreamGeometry. This geometry type can represent all the same shapes as a PathGeometry, but you cannot modify it once it has been created. This is because it does not support the object model of path figures and segments—from markup, it only supports the path syn- tax. (If you are using code, you also can build a StreamGeometry with a StreamGeometryContext object, which lets you describe the shape with a series of method calls.) Because a StreamGeometry is immutable and because it does not maintain a tree of objects representing the shape, it can use a more efficient internal representation than a PathGeometry. If you are working with very complex shapes, or a large num- ber of shapes, this can significantly improve performance. If you are using XAML in such scenarios, you should prefer the path syntax over the object tree, because when you set Path.Data with the path syntax, WPF creates a StreamGeometry instead of a PathGeometry. We have now examined all of the shapes on offer. However, not all visuals are best represented with scalable shapes—sometimes we need to work with bitmap images. Bitmaps WPF supports bitmaps in any of the following formats:* BMP, JPEG, PNG, TIFF, Windows Media Photo, GIF, and ICO (Windows icon files). You can use any image format to create a brush with which to paint any shape or text, as discussed later in the “ImageBrush” section of this chapter. The System.Windows.Media.Imaging namespace provides classes that let you work with the pixels and metadata of image files. However, the simplest way to use a bitmap is with the Image element. * The imaging system is extensible, so it’s possible to add support for custom formats. This requires unman- aged COM components to be written, and is beyond the scope of this book. See http://msdn2.microsoft.com/ en-us/library/ms737408.aspx (http://tinysells.com/111) for information about the API for extending WPF imaging. 430 | Chapter 13: Graphics Image Image simply displays an image. It derives from FrameworkElement, so you can place it anywhere in the visual tree, and it obeys the normal layout rules. You tell it what image to display by setting its Source property, as shown in Example 13-27. Setting the Source property to an absolute URL causes the image to be downloaded and displayed. Alternatively, if you embed an image file in your application as a resource, as described in Chapter 12, you can refer to it with a relative URL, as Example 13-28 illustrates. The Image element is able to resize the image. The exact behavior depends on your application’s layout. If your layout permits the Image element to size to content, it will show the image at its natural size. (We discussed sizing to content in Chapter 3.) For example, the Canvas panel never imposes a particular size on its children, so the code in Example 13-29 will display the image at its native size. However, if your layout provides the Image element with a specific amount of space, by default the bitmap will be scaled to fill that space. A window’s content is con- strained by the size of the window, so Example 13-30 will enlarge or reduce the image to fill the window. The default scaling behavior is to use the same scale factor horizontally and verti- cally. If the available space is the wrong shape for the image, it will be made as large as possible without being too large in either dimension. Figure 13-33 shows the result of Example 13-30, and even though the Image element fills the whole window, Example 13-27. Image element Example 13-28. Using an image resource Example 13-29. Showing an image at its natural size Example 13-30. Scaling an image to fill the available space Bitmaps | 431 the window’s white background is visible above and below when the window is too tall, or to the left and right where the window is too wide. Images with a transparency channel are handled correctly—whatever is behind the image is visible through the transparent parts of the bitmap. If you want the image to fill all of the space even when it is the wrong shape, you can set the element’s Stretch property. This defaults to Uniform, but Fill or UniformToFill will cause the image to fill the full space. These values mean exactly the same as they do for the Shape types—Shape and Image use the same Stretch enu- meration type. So, as Figure 13-34 shows, Fill will distort the image if necessary to make it fit, whereas UniformToFill scales uniformly and then crops if required. The examples we’ve seen so far set the Image element’s Source property with a URL. In fact, the Source property’s type is ImageSource. XAML automatically uses the appropriate type converter to turn the URL into an ImageSource, but when working in code, you will use image source objects directly. Figure 13-33. Uniform stretching Figure 13-34. Fill (left) and UniformToFill (right) 432 | Chapter 13: Graphics ImageSource ImageSource is an abstract base class used throughout WPF to represent an image. Not only does the Image element’s Source property use this type, but so do the ImageBrush class and the visual layer’s DrawingContext.DrawImage method, both of which are described later. Two classes derive from ImageSource: DrawingImage and BitmapSource. DrawingImage has nothing to do with bitmaps—it wraps a resolution-independent drawing object. (Drawings are described later.) This means that elements capable of using an image source can work with either resolution-independent drawings or bitmaps. But because we’re looking at bitmaps right now, BitmapSource is the more interesting class. It too is abstract. Table 13-6 lists the derived concrete bitmap source types. BitmapImage is the simplest to use of these sources. You can give it a URL just as you would in XAML, as shown in Example 13-31. As you can see from Table 13-6, many of the bitmap source types are wrappers around other bitmap sources. You can chain sources together to perform operations such as rotation and cropping. This chaining model is used because you are often not able to modify the original image in any way—it might be compiled into your appli- cation as a resource, or it might live on an external web site. It might seem that the obvious way to handle this would be to load a bitmap and then modify it. However, this is at odds with how images are normally handled. In-memory copies are typi- cally transient—WPF does not cache images unless you explicitly tell it to, either by using CachedBitmap or by setting the CacheOptions property of a BitmapImage. Table 13-6. BitmapSource types Type Usage BitmapFrame A single frame from a bitmap file (some file formats support multiple frames). BitmapImage Represents abitmap ataspecified URL; this isthe type created when specifying aURL inXAML. CachedBitmap Wraps around any BitmapSource and caches it. ColorConvertedBitmap Wraps around any BitmapSource and converts it from one color space to another. CroppedBitmap Wraps around any BitmapSource and presents a cropped version. FormatConvertedBitmap Wraps around any BitmapSource and generates a copy with a different pixel format (e.g., grayscale). RenderTargetBitmap A bitmap whose contents are generated from a Visual. TransformedBitmap Wraps around any BitmapSource and presents a scaled and/or rotated copy. WriteableBitmap A bitmap whose contents can be modified at runtime. Example 13-31. Using BitmapImage Image imageElement = new Image( ); imageElement.Source = new BitmapImage(new Uri( "http://www.nasa.gov/images/content/136054main_bm_072004.jpg")); Bitmaps | 433 That’s not to say you can’t use a load-then-modify approach; it’s just that it’s not necessary for cropping, transformation, color conversion, or pixel format conver- sion. Indeed, the chaining approach offers some advantages. For example, suppose you were writing an application that showed an image and allowed the user to crop it interactively. If you were cropping the image by modifying it, you’d need to keep a copy of the original around just in case the user decided he had cropped a little too much and wanted to go back. If you crop by chaining, say, a BitmapImage to a CroppedBitmap, you never modify the original image, so resetting the cropping is easy. (It’s also more efficient—where possible, WPF avoids generating a copy, and just applies cropping or transformations as it renders.) Sometimes building or modifying bitmaps at runtime is necessary. For example, maybe you want to do something to the image that you cannot achieve by chaining together the built-in image sources, or perhaps you need to build a brand-new image from scratch. RenderTargetBitmap and WriteableBitmap enable you to construct your own bitmaps either from scratch or by modifying a copy of an existing bitmap. Creating Bitmaps RenderTargetBitmap lets you create a new a bitmap from any visual. Example 13-32 renders a red ellipse into a bitmap. You can choose any resolution you like for the output—in this case, we’re creating a 300 dpi bitmap that’s 1 inch wide and 0.5 inches high. Of course, WPF’s coordinate system is resolution-independent—a device-independent pixel is always 1/96 of an inch regardless of the output resolution, so we make the ellipse’s size 96 × 48 device- independent pixels in order to fill the bitmap. Although the RenderTargetBitmap constructor takes a parameter of type PixelFormat, it can create images in the Pbgra32 format only. If you specify anything other than that or PixelFormats.Default, it will throw an exception. Pbrga32 is a 32-bit-per-pixel format, with a pre- multiplied alpha channel. Example 13-32. Using RenderTargetBitmap RenderTargetBitmap bmp = new RenderTargetBitmap( 300, 150, // Dimensions in physical pixels 300, 300, // Pixel resolution (dpi) PixelFormats.Pbgra32); Ellipse e = new Ellipse( ); e.Fill = Brushes.Red; e.Measure(new Size(96, 48)); e.Arrange(new Rect(0, 0, 96, 48)); bmp.Render(e); 434 | Chapter 13: Graphics Example 13-32 renders just one element, but because you can pass any visual, you are free to pass elements that have children, such as Grid and Canvas, enabling you to render multiple elements. However, there are two things to be aware of: • If the visual is not already a visible part of a UI, it is your responsibility to call Measure and Arrange so that it knows how big it needs to be. The generated bit- map will be empty if you fail to do this. • If you create a standalone visual with no parent, as Example 13-32 does, con- trols will not pick up their default styles and will be invisible. Consequently, only primitive elements such as Ellipse or TextBlock will appear. If you want to generate a bitmap of a UI, showing the UI in a real window before passing it to the Render method will fix this. RenderTargetBitmap lets you build a bitmap out of any combination of WPF visuals. This provides a way to modify existing bitmaps. For example, if you want to overlay a text caption onto a bitmap, you could create a Grid containing an Image element displaying the original image, as well as a TextBlock containing the caption, and pass the Grid to the Render method, as shown in Example 13-33. Example 13-33. Adding a caption to a bitmap BitmapImage originalBmp = new BitmapImage( ); originalBmp.BeginInit( ); originalBmp.UriSource = new Uri( "http://www.interact-sw.co.uk/images/M3/BackOfM3.jpeg"); originalBmp.DownloadCompleted += delegate { Grid rootGrid = new Grid(); Image img = new Image(); img.Source = originalBmp; rootGrid.Children.Add(img); TextBlock caption = new TextBlock(); caption.Text = "Ian’s car"; caption.FontSize = 35; caption.Foreground = Brushes.White; caption.Background = new SolidColorBrush(Color.FromArgb(128, 0, 0, 0)); caption.VerticalAlignment = VerticalAlignment.Bottom; caption.HorizontalAlignment = HorizontalAlignment.Center; caption.Margin = new Thickness(5); caption.Padding = new Thickness(5); caption.TextAlignment = TextAlignment.Center; caption.textWrapping = TextWrapping.Wrap; rootGrid.Children.Add(caption) RenderTargetBitmap bmp = newRenderTargetBitmap( originalBmp.PixelWidth, originalBmp.PixelHeight, originalBmp.DpiX, originalBmp.DpiY, PixelFormats.Pbgra32); rootGrid.Measure(new Size(originalBmp.Width, originalBmp.Height)); rootGrid.Arrange(new Rect(0, 0, originalBmp.Width, originalBmp.Height)); bmp.render(rootGrid); Bitmaps | 435 This brings to the surface something that was not previously evident: bitmaps are downloaded from the Web in the background. Normally this isn’t a problem—if you connect a BitmapImage directly into an Image or ImageBrush, WPF automatically updates the display once the image is available. However, we’re now trying to build a new image based on the original, so we must wait for the original image to arrive before we start. This is why most of the work is done in the BitmapImage object’s DownloadCompleted event handler. Example 13-33 does not modify the original bitmap file—in this case, it’s up on a public web server, so there’s no way the code could change it. Instead, the newly cre- ated RenderTargetBitmap contains the modified image. Figure 13-35 shows how it looks. (If you want to write the modified image out to disk, we’ll see how to do that shortly.) RenderTargetBitmap is great if you want to build or modify a bitmap using WPF ele- ments. However, if you want to work with raw pixel data, WriteableBitmap is a bet- ter choice. Example 13-34 uses this technique to invert all of the colors in a bitmap to form a negative image—something you could not do with a RenderTargetBitmap. // bmp now ready for use ... }; originalBmp.EndInit(); Figure 13-35. Bitmap modified with caption Example 13-34. Modifying pixels BitmapImage originalBmp = new BitmapImage( ); originalBmp.BeginInit( ); originalBmp.UriSource = new Uri( "http://www.interact-sw.co.uk/images/M3/BackOfM3.jpeg"); originalBmp.DownloadCompleted += delegate { BitmapSource prgbaSource = new FormatConvertedBitmap(originalBmp, PixelFormats.Pbgra32, null, 0); WriteableBitmap bmp = new WriteableBitmap(prgbaSource); Example 13-33. Adding a caption to a bitmap (continued) 436 | Chapter 13: Graphics As before, the code waits until the image has been downloaded before proceeding. Once the image is available, the first thing the code does is ensure that the pixel data will be available in the format our code expects by wrapping the original in a FormatConvertedBitmap. If the original image uses a different pixel format, this will convert it for us. Next, we load the results into a WriteableBitmap. We read out the pixel values using CopyPixels. CopyPixels is not unique to WriteableBitmap—you can read pixel values from any bitmap source—but only WriteableBitmap offers a WritePixels method to change the image. After we’ve flipped the bits of the red, green, and blue channels of the pixel, we use WritePixels to put these modified pixels back into the bitmap. Finally, we can use the WriteableBitmap as an image source—for example, we could set it as an Image element’s Source property. Figure 13-36 shows the resulting nega- tive image. If you are generating bitmaps with either WriteableBitmap or RenderTargetBitmap, you may not want to put the results on-screen—you might want to write them out to disk. You can do this with a bitmap encoder. int w = bmp.PixelWidth; int h = bmp.PixelHeight; int[] pixelData = new int[w * h]; int widthInBytes = 4 * w; bmp.CopyPixels(pixelData, widthInBytes, 0); for (int i = 0; i < pixelData.Length; ++i) { pixelData[i] ^= 0x00ffffff; } bmp.WritePixels(new Int32Rect(0, 0, w, h), pixelData, widthInBytes, 0); // bmp now ready for use ... }; originalBmp.EndInit( ); Figure 13-36. Bitmap with inverted colors Example 13-34. Modifying pixels (continued) Bitmaps | 437 Bitmap Encoders and Decoders A bitmap encoder is a class that knows how to generate a bitmap stream in a particu- lar format. WPF provides encoders for all the image formats listed earlier. Encoders are named after their format (e.g., PngBitmapEncoder, JpegBitmapEncoder, etc.). Example 13-35 shows how to write a bitmap out to disk as a JPEG file. This func- tion works with any BitmapSource. Decoders work in the opposite direction—they know how to read bitmap streams of a particular format. Decoders are used implicitly whenever you load a bitmap stream into a BitmapImage, but you can also use them explicitly. This is necessary if you wish to access bitmap metadata or retrieve all the frames in a multiframe image file. Example 13-36 shows how to load a JPEG image with a decoder to discover the type of camera used to take the image. Notice that both the encoder and the decoder have a property called Frames to repre- sent the frames of the image. For single-frame formats, this cannot contain more than one frame, but an animated GIF would contain multiple frames. Metadata is returned only at the level of individual frames. The decoder classes all offer a Metadata property, but it is always null for all of the decoders that ship as part of the first release of WPF. Use the Metadata property of the frame instead. Example 13-35. Creating a JPEG file static void WriteJpeg(string fileName, int quality, BitmapSource bmp) { JpegBitmapEncoder encoder = new JpegBitmapEncoder( ); BitmapFrame outputFrame = BitmapFrame.Create(bmp); encoder.Frames.Add(outputFrame); encoder.QualityLevel = quality; using (FileStream file = File.OpenWrite(fileName)) { encoder.Save(file); } } Example 13-36. Reading bitmap metadata static string GetCamera(string myJpegPath) { JpegBitmapDecoder decoder = new JpegBitmapDecoder(new Uri(myJpegPath), BitmapCreateOptions.None, BitmapCacheOption.None); BitmapMetadata bmpData = (BitmapMetadata) decoder.Frames[0].Metadata; return bmpData.CameraModel; } 438 | Chapter 13: Graphics There is one last bitmap feature we will examine. It is a little different from the rest, because it allows bitmap processing to be applied to any part of the UI, not just bitmaps. Bitmap Effects All user interface elements have a BitmapEffects property. You can use it to apply a visual effect to the element and all of its children. All of these effects use bitmap pro- cessing algorithms, hence the name. Example 13-37 applies a BlurBitmapEffect to one of its StackPanel elements. As you can see in Figure 13-37, the righthand side is out of focus, thanks to the blur effect. Despite this, it’s still live—as you can see, the radio button on the righthand side has been selected. Even if you made the panel completely illegible by cranking up the blur’s Radius property to 10, the controls would continue to function because WPF’s input handling completely ignores bitmap effects. Table 13-7 lists all of the built-in effects. It is possible to write custom effects, but this requires an unmanaged COM component to be written, and is beyond the scope of this book.* Example 13-37. Bitmap effect Figure 13-37. BlurBitmapEffect * See http://msdn2.microsoft.com/en-us/library/ms735322.aspx(http://tinysells.com/108) for information on the API for building custom WPF bitmap effects. Brushes and Pens | 439 Bitmap effects are expensive. Use them sparingly. To apply a bitmap effect, WPF must first render the content to which the effect will be applied, and then run the bitmap effect algorithm over the rendered content in order to generate the final results. This has two significant performance implications. First, it involves the cre- ation of an intermediate render target—a block of memory in which to build the rendered content prior to processing. This increases mem- ory usage. Second, many bitmap effects run in software. This in turn means that the content to which the effect is applied will be rendered in software. Tempting though it may be, applying a bitmap effect to a large region (e.g., the whole window) is a very bad idea. It disables hardware ren- dering for the whole region, which is likely to reduce performance drastically. So far, we’ve seen how to use bitmaps and define simple shapes, but we have been rather unadventurous in our choice of fills and outlines for our shapes. We have used nothing but standard named colors and simple outline styles. And although we’ve seen how to render bitmaps as standalone rectangles of content, we’ve not yet seen how we can combine bitmaps with shapes. So, it’s time to look at how WPF’s brush and pen classes enable more interesting drawing styles. Brushes and Pens To draw a shape on the screen, WPF needs to know how you would like that shape to be colored in and how its outline should be drawn. WPF provides several Brush types supporting a variety of painting styles. The Pen class extends this to provide information about stroke thickness, dash patterns, and the like. In this section, we will look at all of the available brush types and the Pen class. How- ever, because all brushes and pens are ultimately about deciding what colors to use where and how they are combined, we must first look at how colors are represented. Table 13-7. BitmapEffects Type Usage BevelBitmapEffect Creates a pseudo-3D relief effect at the edges of the content BitmapEffectGroup Allows multiple effects to be used on a single element BlurBitmapEffect Makes the image look out of focus DropShadowBitmapEffect Draws a soft shadow around the outline of the content EmbossBitmapEffect Performs a bump mapping algorithm to apply a pseudo-3D relief across the whole of the content OuterGlowBitmapEffect Adds a soft halo around the outline of the content 440 | Chapter 13: Graphics Color WPF uses the Color structure in the System.Windows.Media namespace to represent a color. If you have worked with Windows Forms, ASP.NET, or GDI+ in the past, note that this is not the same structure as those technologies use. They use the Color structure in the System.Drawing namespace. WPF introduces this new Color struc- ture because it can work with floating-point color values, enabling much higher color precision and greater flexibility. The Color structure uses four numbers or “channels” to represent a color. These channels are red, green, blue, and alpha. Red, green, and blue channels are the tradi- tional way of representing color in computer graphics. (This is because color screens work by adding these three primary colors together.) A value of 0 indicates that the color component is not present at all; 0 on all three channels corresponds to black. The alpha channel represents the level of opacity—a Color can be opaque, com- pletely transparent, or anywhere in between these two extremes. WPF’s composi- tion engine allows anything to be drawn with any level of transparency. A value of 0 is used to represent complete transparency, and 1 means completely opaque. Windows has traditionally used 24 bits of color information (8 bits per channel) to represent “true” or “full” color, and 32 bits for full color with transparency. This is just about sufficient for the average computer screen. The color and brightness range of most computer displays means that 24 bits of color has always been adequate (albeit barely) for most purposes, although a sufficiently good screen can reveal the limitations. However, for many imaging applications, this is not sufficient. For exam- ple, film can accommodate a much wider range of brightness than a computer screen, so 24-bit color is simply not good enough for graphics work with film as its output medium. The same is true for many medical imaging applications. And, even for computer or video images, 24-bit color can cause problems—if images are going through many stages of processing, these can amplify the limitations of 24-bit source material. WPF therefore supports a much higher level of detail in its representation of color. Each color channel uses 16 bits instead of 8. The Color structure still supports the use of 8-bit channels where required, because a lot of imaging software depends on such a representation. Color exposes the 8-bit channels through the A, R, G, and B properties, which accept values in the range of 0–255.* The higher definition repre- sentations are available through the ScA, ScR, ScG, and ScB properties, which present the channels as single-precision floating-point values ranging from 0–1. * The old GDI+ Color structure exposed 8-bit properties of the same names, which may be useful if you need to port code. Brushes and Pens | 441 The “Sc” in the ScA, ScR, ScG, and ScB properties refers to the fact that these support the standard “Extended RGB colour space—scRGB” color space defined in the IEC 61966-2-2 specification. (This is an international specification, hence the u in colour.) Strangely, the sc is not officially short for anything. During its development, the scRGB specwent through various names. As is often the way with standards committees, various parties had objections to the names that made any sense, so they settled on something unobjectionable but meaningless. Various post hoctheories as to what sc might stand for have been developed. One is that it is an abbreviation of specular, suggesting the high-headroom support offered by the out-of-gamut capability. Another theory is that it could be short for standard compositing,to indicate that the color space is designed for compositing rather than for physical devices. This same thinking informed the theory that it is short for scene referred (although one member of the standards com- mittee maintains that this last theory is absolutely wrong). The Color class also allows color values to go out of range. This can lead to counter- intuitive color values where a particular color channel may be negative, or more than 100 percent. Even though this may seem to make no sense, it can be useful to accom- modate excursions outside of the 0–1 range if you are performing several image pro- cessing steps. For example, suppose you want to increase the brightness but decrease the contrast of an image—the first step might take the brightness over 100 percent, but the second step could bring it back into range. As long as your final output val- ues are within the 0–1 range, it doesn’t necessarily matter where they went during image processing. However, if a color system is unable to accommodate out-of-range values, it must clip all colors to be within the valid range at every single stage. This range limiting can result in a degradation of image quality. There is also a Colors class. This provides a set of standard named colors, with all the old favorites such as PapayaWhip, BurlyWood, LightGoldenrodYellow, and Brown. You cannot use a Color directly for drawing. To draw, you need either a Brush or a Pen. SolidColorBrush SolidColorBrush is the simplest brush. It uses one color across the whole area being painted. It has just one property, Color. Note that this color is allowed to use trans- parency, despite what the word Solid suggests. We have already been using the SolidColorBrush extensively even though we have not yet referred to it by name. This is because WPF creates this kind of brush if you specify the name of a color in markup—if you work mostly with XAML, you very rarely need to specify that you require a SolidColorBrush, because you’ll get one by default. (The only reason you would normally specify it in full is if you want to use data binding with the brush’s properties.) Consider this example: 442 | Chapter 13: Graphics The XAML compiler will recognize Yellow as one of the standard named colors from the Colors class, and will supply a suitable SolidColorBrush. (See Appendix A for more information on how XAML maps from strings to property values.) It does not need to create the brush, because there is a Brushes class, providing a set of brushes for each of the named colors in Colors. You will also be provided with a SolidColorBrush if your markup uses a numeric color value. Example 13-38 shows various examples of numeric colors. All but the last two begin with a # symbol and contain hexadecimal digits. A three-digit num- ber is taken to be one digit each of red, green, and blue. A four-digit number is inter- preted as alpha, red, green, and blue. These are compact formats providing just 4 bits per channel. Six- or eight-digit numbers allow 8 bits per channel for RGB or ARGB, respectively. To exploit the full accuracy of scRGB, you provide a string that starts with “sc#” followed by a space, and then four comma-separated decimal numbers representing the A, R, G, and B values. Finally, if the string starts with ContextColor, you can define a color that refers to a specific International Color Consortium (ICC) or Image Color Manager (ICM) color profile file. The SolidColorBrush is lightweight and straightforward. However, it makes for fairly flat-looking visuals. WPF offers some more interesting brushes if you want to make your user interface look a little more appealing. LinearGradientBrush With a LinearGradientBrush, the painted area transitions from one color to another, or even through a sequence of colors. Figure 13-38 shows a simple example. This brush fades from black to white, starting at the top-left corner and finishing at the bottom-right corner. The fade always runs in a straight line—this brush cannot do curved transitions, hence the name “linear.” Example 13-39 shows the markup for Figure 13-38. Example 13-38. Numeric color values Figure 13-38. LinearGradientBrush Brushes and Pens | 443 The StartPoint and EndPoint properties indicate where the color transition begins and ends. These coordinates are relative to the area being filled, so 0,0 is the top left and 1,1 is the bottom right, as shown in Figure 13-39. (Note that if the brush is painting an area that is narrow or wide, the coordinate system is squashed accord- ingly.) You are allowed to put the StartPoint and EndPoint outside of the rectangle. For example, you could change the StartPoint of Figure 13-39 to –1,0. This would mean that only half of the fill’s color range would be used. This might seem point- less—setting the first gradient stop’s color to a shade of gray would have the same effect. However, sometimes it’s easier to tweak the look of a fill by adjusting the end points rather than by adjusting the colors. Each GradientStop has an Offset property as well as a Color. This enables the fill to pass through multiple colors. Example 13-40 shows a LinearGradientBrush with mul- tiple colors. Figure 13-40 shows the result. Example 13-39. Using a LinearGradientBrush Figure 13-39. Fill coordinate system Example 13-40. Multiple gradient stops 0, 0 0, 1 1, 0 1, 1 444 | Chapter 13: Graphics LinearGradientBrush is often used to provide a feeling of depth to a user interface. Example 13-41 shows a typical example. It uses just two shapes—a pair of rounded Rectangle elements. (The Grid doesn’t contribute directly to the appearance. It is there to make it easy to resize the graphic—changing the grid’s Width and Height will cause both rectangles to resize appropriately.) The second rectangle’s gradient fill fades from a partially transparent shade of white to a completely transparent color, which provides an interesting visual effect. Figure 13-41 shows the result. This is an extremely simple graphic, containing just two shapes. The use of gradient fills has added an impression of depth that these shapes would otherwise not have conveyed. Figure 13-40. Multiple gradient stops (Color Plate 22) Example 13-41. Simulating lighting effects with linear fills Brushes and Pens | 445 RadialGradientBrush RadialGradientBrush is very similar to LinearGradientBrush. Both transition through a series of colors. But whereas LinearGradientBrush paints these transitions in a straight line, the RadialGradientBrush fades from a starting point out to an elliptical boundary. This opens more opportunities for making your user interface appear less flat. Example 13-42 shows an example. The RadialGradientBrush takes a list of GradientStop objects to determine the colors that the fill runs through, just like LinearGradientBrush. This example uses the RadiusX and RadiusY properties to determine the size of the elliptical boundary, and the Center property to set the position of the ellipse. The values chosen here make the fill boundary fit entirely into the shape, as Figure 13-42 shows. The area of the shape that falls outside of this boundary is filled with the color of the final GradientStop. Notice that the focal point of the fill is to the left. This is because the GradientOrigin has been set. (By default, the focal point is in the center of the ellipse.) Example 13-42 makes it easy to see the effects of the properties of the RadialGradientBrush, but it’s not a very exciting example. Example 13-43 shows something a little more adventurous. It is similar to Example 13-41—both use a Figure 13-41. Simple lighting effects with linear fills (Color Plate 23) Example 13-42. Using a RadialGradientBrush Figure 13-42. Simple radial fill (Color Plate 24) 446 | Chapter 13: Graphics small number of shapes with gradient fills to convey a feeling of depth and reflec- tion—but this example uses radial fills as well as a linear fill. This time, three ellipses have been used. Two have RadialGradientBrush fills, and one has a LinearGradientBrush stroke. The fill in the first ellipse creates the glow at the bottom of the drawing. The second adds the reflective highlight at the top. The third draws a bezel around the outside. Figure 13-43 shows the result. The radial fills suggest a curved surface and give the graphic a slightly translucent look. Example 13-43. Radial gradient fills Brushes and Pens | 447 ImageBrush, DrawingBrush, and VisualBrush The ability to fill shapes with a pattern or image of some kind is often useful. WPF provides three brushes that allow us to paint shapes with whatever graphics we choose. The ImageBrush lets us paint with a bitmap. With DrawingBrush, we use a scalable drawing. VisualBrush allows us to use any UI element as the brush image— we can in effect use one piece of our user interface to paint another. All of these brushes have a certain amount in common, so they all derive from the same base class, TileBrush. TileBrush ImageBrush, DrawingBrush, and VisualBrush all paint using some form of source pic- ture. Their base class, TileBrush, decides how to stretch the source image to fill the available space, whether to repeat (tile) the image, and how to position the image within the shape. TileBrush is an abstract base class, so you cannot use it directly. It exists to define the features common to the ImageBrush, DrawingBrush, and VisualBrush. Figure 13-44 shows the default TileBrush behavior. This figure shows three rectan- gles so that you can see what happens when the brush is made narrow or wide, as well as how it looks when the brush shape matches the target area shape. All three are rectangles painted with an ImageBrush specifying just the image. Figure 13-43. Radial fills (Color Plate 25) Figure 13-44. Default stretching and placement (Stretch.Fill) 448 | Chapter 13: Graphics The stretching behavior would be exactly the same for any of the tile brushes—we are using ImageBrush just as an example. Indeed, all the features discussed in this sec- tion apply to any TileBrush. Example 13-44 shows the markup used for each rectan- gle in Figure 13-44. Because this specifies nothing more than which image to display, it gets the default TileBrush behavior: the brush has stretched the source image to fill the available space. We can change this behavior by modifying the brush’s Stretch property. It defaults to Fill, but we can show the image at its native size by specifying None,as Example 13-45 shows. The None stretch mode preserves the aspect ratio, but if the image is too large, it will simply be cropped to fit the space available, as Figure 13-45 shows. For displaying images, you may want to stretch the image to match the available space without distorting the aspect ratio. TileBrush supports this with the Uniform stretch mode, shown in Figure 13-46. This scales the source image so that it fits entirely within the space available. Example 13-44. Using an ImageBrush Example 13-45. Specifying a Stretch of None Figure 13-45. Stretch.None Brushes and Pens | 449 The Uniform stretch mode typically results in the image being made smaller than the area being filled, leaving the remainder of the space transparent. Alternatively, you can scale the image so that it completely fills the space available while preserving the aspect ratio, cropping in one dimension if necessary. The UniformToFill stretch mode does this, and it is shown in Figure 13-47. UniformToFill is most appropriate if you are filling an area with some nonrepeating textured pattern, because it guarantees to paint the whole area. It is probably less appropriate if your goal is simply to display a picture—as Figure 13-47 shows, this stretch mode will crop images where necessary. If you want to show the whole pic- ture, Uniform is the best choice. All of the stretch modes except for Fill present an extra question: how should the image be positioned? With None and UniformToFill, cropping occurs, so WPF needs to decide which part of the image to show. With Uniform, the image may be smaller than the space being filled, so WPF needs to decide where to put it. Images are centered by default. In the examples where the image has been cropped (Figures 13-45 and 13-47) the most central parts are shown. In the case of Uniform, where the image is smaller than the area being painted, it has been placed in the mid- dle of that area (Figure 13-46). You can change this with the AlignmentX and Figure 13-46. Stretch.Uniform Figure 13-47. Stretch.UniformToFill 450 | Chapter 13: Graphics AlignmentY properties. You can set these to Left, Middle,orRight, and Top, Middle,or Bottom, respectively. Example 13-46 shows the UniformToFill stretch mode again, but this time with alignments of Left and Bottom. Figure 13-48 shows the results. The stretch and alignment properties are convenient to use, but they do not allow you to focus on any arbitrary part of the image, or choose specific scale factors. TileBrush supports these features through the Viewbox, Viewport, ViewboxUnits, and ViewportUnits properties. The Viewbox property chooses the portion of the image to be displayed. By default, this property is set to encompass the whole image, but you can change it to focus on a particular part. Figure 13-49 shows the UniformToFill stretch mode, but with a Viewbox set to zoom in on the front of the car. Example 13-46. Specifying a Stretch and alignment Figure 13-48. Stretch.UniformToFill, bottom-left-aligned Figure 13-49. Stretch.UniformToFill with Viewbox Brushes and Pens | 451 As Example 13-47 shows, the Viewbox is specified as four numbers. The first two are the coordinates of the upper-lefthand corner of the Viewbox; the second two are the width and height of the box. By default, coordinates of 1,1 represent the entire source image. Sometimes it can be more convenient to work in the coordinates of the source image itself. As Example 13-48 shows, you can do this by setting the ViewboxUnits property to Absolute. (It defaults to RelativeToBoundingBox.) In this case, because an ImageBrush is being used, these are coordinates in the source bitmap. In the case of a DrawingBrush or VisualBrush, the Viewbox would use the coordinate system of the source drawing. Although the last two examples chose which portion of the source image to focus on by specifying a Viewbox, they still relied on the Stretch property to choose how to size and position the output. If you want more precise control, you can use Viewport to choose exactly where the image should end up in the brush. Figure 13-50 illustrates the relationship between Viewbox and Viewport. On the left is the source image—a bitmap, in this case, but it could also be a drawing or visual tree. The Viewbox specifies an area of this source image. On the right is the brush. The Viewport specifies an area within this brush. WPF will scale and position the source image so that the area specified in Viewbox ends up being painted into the area specified by Viewport. Example 13-47. Specifying a Viewbox Example 13-48. Viewbox with absolute units Figure 13-50. Viewbox and Viewport 452 | Chapter 13: Graphics As well as indicating where the contents of the Viewbox end up, the Viewport speci- fies the extent of the brush; it will be clipped to the size of the Viewport. Example 13-49 shows Viewport and Viewbox settings that correspond to the areas highlighted in Figure 13-50. Like the Viewbox, by default the Viewport coordinates range from 0–1. The position 0,0 is the top left of the brush, and 1,1 is the bottom right. This means that the part of the image shown by the brush will always be the same, regardless of the brush size or shape. This results in a distorting behavior similar to the default StretchMode of Fill, as shown in Figure 13-51. (In fact, the Fill stretch mode is equivalent to set- ting the Viewbox and Viewport to be 0,0,1,1.) As with the Viewbox, you can specify different units for the Viewport. The ViewportUnits property defaults to RelativeToBoundingBox, but if you change it to Absolute, the Viewport is measured using output coordinates. Note that setting the Viewport in absolute units means the image will no longer scale as the brush resizes. In several of the preceding examples, the source image has not completely filled the area of the brush. By default, the brush is transparent in the remaining space. How- ever, if you have specified a Viewport, you can choose other behaviors for the spare space with the TileMode property. The default is None, but if you specify Tile,as Example 13-50 does, the image will be repeated to fill the space available. Example 13-49. Using Viewbox and Viewport Figure 13-51. Viewbox and Viewport Example 13-50. Specifying a Stretch and a TileMode Brushes and Pens | 453 Figure 13-52 shows the effect of the Tile tile mode. There is one potential problem with tiling. It can often be very obvious where each repeated tile starts. If your goal is simply to fill in an area with a texture, these discontinuities can jar somewhat. To alleviate this, TileBrush supports three other modes of tiling: FlipX, FlipY, and FlipXY. These mirror alternate images as shown in Figure 13-53. Although mirroring can reduce the discontinuity between tiles, for some source images it can change the look of the brush quite substantially. Flipping is typically better suited to more uni- form texture-like images than pictures. Remember that all of this scaling and positioning functionality is common to all of the brushes derived from TileBrush. However, some features are specific to the indi- vidual brush types, so we will now look at each in turn. ImageBrush ImageBrush paints areas of the screen using a bitmap. The ImageBrush was used to cre- ate all of the pictures in the preceding section. This brush is straightforward—you simply need to tell it what bitmap to use with the ImageSource property, as Example 13-51 shows. Figure 13-52. Tiling Figure 13-53. FlipXY tiling 454 | Chapter 13: Graphics To make a bitmap file available to the ImageBrush, you can add one to your project in Visual Studio. The file in Example 13-51 was in a subdirectory of the project called Images, and was built into the project as a resource. To do this, select the bitmap file in Visual Studio’s Solution Explorer and then, in the Properties panel, make sure the Build Action property is set to Resource. This embeds the bitmap into the executable, enabling the ImageBrush to find it at runtime. (See Chapter 12 for more information on how binary resources are managed.) Alternatively, you can specify an absolute URL for this property—you could, for example, display an image from a web site.* ImageBrush is quite happy to deal with images with a transparency channel (also known as an alpha channel). Not all image formats sup- port partial transparency, but some—such as the PNG, WMP, and BMP formats—can. (And, to a lesser extent, GIF. It supports only fully transparent or fully opaque pixels. This is effectively a 1-bit alpha channel.) Where an alpha channel is present, the ImageBrush will honor it. DrawingBrush The ImageBrush is convenient if you have a bitmap you need to paint with. However, bitmaps do not fit in well with resolution independence. The ImageBrush will scale bitmaps correctly for your screen’s resolution, but bitmaps tend to become blurred when scaled. DrawingBrush does not suffer from this problem, because you usually provide a scalable vector image as its source. This enables a DrawingBrush to remain clear and sharp at any size and resolution. The vector image is represented by a Drawing object. This is an abstract base class. You can draw shapes with a GeometryDrawing—this allows you to construct draw- ings using all of the same geometry elements supported by Path. You can also use bit- maps and video with ImageDrawing and VideoDrawing. Text is supported with GlyphRunDrawing. Finally, you can combine these using the DrawingGroup. Even if you use nothing but shapes, you will still probably want to group the shapes with a DrawingGroup. Each GeometryDrawing is effectively equivalent to a single Path, so if you want to draw using different pens and brushes, or if you want your shapes to overlap rather than combine, you will need to use multiple GeometryDrawing elements. Example 13-51. Using an ImageBrush * If you don’t use an absolute URL, a property of type ImageSource will be treated as a relative pack URI. So, the image in Example 13-51 is handled as a relative pack URI, which resolves to a resource compiled into the component. Pack URIs and resources were described in Chapter 12. Brushes and Pens | 455 Example 13-52 shows a Rectangle that uses a DrawingBrush for its Fill. This brush paints the same visuals seen earlier in Figure 13-41. Because each rectangular ele- ment that makes up the drawing uses different linear gradient fills, they both get their own GeometryDrawing, nested inside a DrawingGroup. Example 13-52. Using DrawingBrush 456 | Chapter 13: Graphics With a DrawingBrush, the Viewbox defaults to 0,0,1,1. All of the coordinates and sizes in Example 13-52 are relative to this coordinate system. If you would prefer to work with coordinates over a wider range, you can simply set the Viewbox to the range you require, and the ViewboxUnits to Absolute. We already saw how to use the Viewbox in Example 13-47. The only difference with DrawingBrush is that you’re using it to indi- cate an area of the drawing, rather than a bitmap. Note that we can use the Viewbox to focus on some subsection of the picture, just as we did earlier with the ImageBrush. We can modify the DrawingBrush in Example 13-52 to use a smaller Viewbox, as shown in Example 13-53. The result of this is that most of the drawing is now outside of the Viewbox, so the brush shows only a part of the whole drawing, as Figure 13-54 shows. DrawingBrush is very powerful, as it lets you use more or less any graphics you like as a brush, and because it is vector-based, the results remain crisp at any scale. It does have one drawback if you are using it from markup, though: it is somewhat cumbersome to use from XAML. Consider that Example 13-52 produces the same appearance as Example 13-41, but these examples are 48 lines long and 30 lines long, respectively. The DrawingBrush is much more verbose because it requires us to work with geome- try objects rather than higher-level constructs such as the Grid or Rectangle used in Example 13-41. (Note that this problem is less acute when using this brush from code, where the higher-level objects are not much more convenient to use than geometries. The verbosity is really only a XAML issue.) Moreover, higher-level features such as the ability to exploit layout or controls are not available in a DrawingBrush. For- tunately, VisualBrush allows us to paint with these higher-level elements. VisualBrush The VisualBrush can paint with the contents of any element derived from Visual. Because Visual is the base class of all WPF user interface elements, this means that in practice, you can plug any markup you like into a VisualBrush. The brush is “live” in that if the brush’s source visual changes, anything painted with the brush will auto- matically update. Example 13-53. Viewbox and DrawingBrush Figure 13-54. DrawingBrush with small Viewbox Brushes and Pens | 457 Example 13-54 shows a Rectangle filled using a VisualBrush. The brush’s visuals have been copied directly from Example 13-41, resulting in a much simpler brush than the equivalent DrawingBrush. (The results look exactly the same as Figure 13-41—the whole point of the VisualBrush is that it paints areas to look just like the visuals it wraps.) Example 13-54. Using a VisualBrush 458 | Chapter 13: Graphics You might be wondering why on earth you would ever use a DrawingBrush when VisualBrush is so much more flexible— VisualBrush can support any element, whereas DrawingBrush supports only the low-level Drawing and Geometry classes. However, DrawingBrush is more efficient. A drawing doesn’t carry the overhead of a full FrameworkElement for every drawing primitive. Although it takes more effort to create a DrawingBrush, it consumes fewer resources at runtime. If you want your user interface to have particularly intri- cate visuals, the DrawingBrush will enable you to do this with lower overhead. If you plan to use animation, this low overhead may trans- late to smoother-looking animations. VisualBrush makes it very easy to create a brush that looks exactly like some part of your user interface. You could use this to create effects such as reflections, as Figure 13-55 shows, or to project the user interface onto a 3D surface. (We show this latter technique in Chapter 17.) Example 13-55 shows how to create a reflection effect with a VisualBrush. The user interface to be reflected has been omitted for clarity—you would place this inside the Grid named mainUI. The important part is the Rectangle, which has been painted with a VisualBrush based on mainUI. This example also uses a ScaleTransform to flip the image upside down. Figure 13-55. Reflection effect with VisualBrush Example 13-55. Simulating a reflection with VisualBrush ...User interface to be reflected goes here... Brushes and Pens | 459 The reflection is live in only one direction: if the main UI updates, the reflection will update to match, but you cannot interact with it. As you can see from Figure 13-55, the image fades out toward the bottom. We achieved this by applying an OpacityMask. All user interface elements support this OpacityMask property. Its type is Brush. Only the transparency channel of the brush is used; the opacity of the element to which the mask is applied is determined by the opacity of the brush. In this case, we’ve used a LinearGradientBrush that fades to transparent, and this is what causes the Rectangle to fade to transparency. Remember that VisualBrush derives from TileBrush. This means that you are not obliged to paint the target element with the whole of the source visual—you can use Viewport and Viewbox to be more selective. For example, you could use this to imple- ment a magnifying glass feature.* Pen Brushes are used to fill the interior of a shape. To draw the outline of a shape, WPF needs a little more information—not only does it need a brush in order to color in the line, but it also needs to know how thick you would like the line to be drawn, and whether you want a dash pattern and/or end caps. The Pen class provides this information. * For an example of this technique, see http://www.interact-sw.co.uk/iangblog/2007/03/28/wpfmagnifyupdate, or http://tinysells.com/100. Example 13-55. Simulating a reflection with VisualBrush (continued) 460 | Chapter 13: Graphics A Pen is always based on a brush, meaning that we can use all of the drawing effects we’ve seen so far when drawing outlines. You set the brush using the Brush property of the Pen class. Remember that if you are working with any of the high-level shape ele- ments, you will not work with a Pen directly. A Pen is used under the covers; you set all of the properties indirectly. Table 13-2 showed how Shape properties correspond to Pen properties. You will typically deal directly with a Pen only if you work at a lower level, such as with the GeometryDrawing in a DrawingBrush. You set the line width with the Thickness property. For simple outlines, this and Brush may be the only properties you set. However, Pen has more to offer. For exam- ple, you can set a dash pattern with the DashArray property. This is simply an array of numbers. Each number corresponds to the length of a particular segment in the dash pattern. Example 13-56 illustrates the simplest possible pattern. This indicates that the first segment in the dash pattern is of length 1. The dash pat- tern repeats, and because only one segment has been specified, every segment will be of length 1. Figure 13-56 shows the result. Example 13-57 shows two slightly more interesting pattern sequences. Note that the second case supplies an odd number of segments. This means that the first time around, the solid segments will be of size 6 and the gap will be of size 1, but when the sequence repeats, the solid segment will be of length 1 and the gaps of size 6. So the effective length of the dash pattern is doubled. Figure 13-57 shows the results of both patterns. Example 13-56. DashArray Figure 13-56. Simple dash pattern Example 13-57. Dash patterns Figure 13-57. Longer dash patterns Transformations | 461 WPF can draw corners in three different ways. You can set the LineJoin property to Miter, Bevel, or Round. These are shown in Figure 13-58. For open shapes such as Line and PolyLine, you can specify the shape of the starts and ends of lines with the StartLineCap and EndLineCap properties. The DashCap property specifies the shape with which dashes start and end. These properties sup- port four styles of caps: Round, Triangle, Flat, and Square. These are shown in Figure 13-59. Flat and Square both square off the ends of lines. The distinction is that with Flat, the flat end intersects the end point of the line, but with Square,it extends beyond it. The amount by which it overshoots the line is equal to half the line thickness. Transformations Support for high-resolution displays is an important feature of WPF. This is enabled in part by the emphasis on the use of scalable vector graphics rather than bitmaps. But as experience with GDI+ and GDI32 has shown, if scalability is not integrated completely into the graphics architecture, resolution independence is very hard to achieve consistently in practice. WPF’s support for scaling is built in at a fundamental level. Any element in the user interface can have a transformation applied, making it easy to scale or rotate any- thing in the user interface. As we saw in Chapter 3, all user interface elements have RenderTransform and LayoutTransform properties. These are of type Transform, which is an abstract base class. There are derived classes implementing various affine transformations,* listed in Table 13-8. Figure 13-58. LineJoin types: Miter, Bevel, and Round Figure 13-59. Line cap styles: Round, Triangle, Flat, and Square *Anaffine transformation is one in which features arranged in a straight line before the transform remain in a straight line after the transform. Note that 3D perspective transformations do not preserve straight lines. 462 | Chapter 13: Graphics Most of these are just convenience classes—you can represent all supported transfor- mations by the MatrixTransform class. This contains a 3 × 3 matrix, allowing any affine transformation to be used. However, the other transform types are often easier to work with than the set of numbers in a matrix. Example 13-58 shows the use of a TransformGroup to apply a ScaleTransform and a RotateTransform to the RenderTransform property of a TextBlock. Notice that we have used a TransformGroup here to combine the effects of two trans- forms. (Note that the rotation angle is specified in degrees in a RotateTransform, rather than radians, which are slightly more common in computational geometry. Likewise, positive numbers are clockwise, contrary to the usual mathematical con- vention.) Figure 13-60 shows the results. The order in which you apply transforms is usually significant, because each trans- form in a TransformGroup builds on the ones before it. For example, if you add a TranslateTransform to Example 13-58 to move the Hello text right by 30 device- independent pixels, the effect is different depending on whether it appears before Table 13-8. Transform types Transform class Usage MatrixTransform General-purpose transform based on 3 × 3 matrix RotateTransform Rotates around a point ScaleTransform Scales in x and/or y SkewTransform Shears (e.g., converts a square into a rhombus) TransformGroup Combines several transforms into one TranslateTransform Moves items by a specified vector Example 13-58. Using RenderTransform Hello, world Figure 13-60. RenderTransform Visual Layer Programming | 463 or after the other transforms. The lefthand side of Figure 13-61 shows the result when the translation occurs first, and the righthand side shows the result when it occurs last. In the first case, the text has moved twice as far to the left; this is because the ScaleTransform was applied after the translation, doubling its effects. Visual Layer Programming The shape elements can provide a convenient way to work with graphics. However, in some situations, creating all the shape elements required to represent a drawing, and adding them to the UI tree, may be more trouble than it’s worth. Data binding can often provide a solution—the shape classes all derive from FrameworkElement,so they can participate in data binding like any other user interface element. However, sometimes your data may be structured in such a way that it’s easier or more effi- cient to write code that performs a series of drawing operations based on the data. For this reason, WPF provides a “visual layer” API as a lower-level alternative to shape elements. (In fact, the shape elements are all implemented on top of this visual layer.) This API lets us write code that renders content on demand. A visual is a visible object. A WPF application’s appearance is formed by composing all of its visuals onto the screen. Because WPF builds on top of the visual layer, every element is a visual—the FrameworkElement base class derives indirectly from Visual. Programming at the visual layer simply involves creating a visual and writing code that tells WPF what we’d like to appear in that visual. Even at this low level, WPF behaves very differently from Win32. The way in which graphics acceleration is managed means that your on-demand rendering code is called much less often than it would be in a classic Windows application. Rendering On Demand The key to custom on-demand rendering is the OnRender method. WPF calls this method when it needs your component to generate its appearance. (This is how the built-in shape classes render themselves.) The virtual OnRender method is defined by the UIElement class. Most elements derive from this indirectly via FrameworkElement, which adds core features such as layout and data binding. Figure 13-61. Adding a TranslateTransform before (left) and after (right) 464 | Chapter 13: Graphics Example 13-59 shows a custom element that overrides OnRender. The OnRender method is passed a single parameter of type DrawingContext. This is the low-level drawing API in WPF. It provides a set of primitive drawing operations, which are listed in Table 13-9. Example 13-59 uses the DrawRectangle and DrawText methods. Note that the DrawingContext uses the Brush and Pen classes to indicate how shapes should be filled and outlined. We can also pass in the same Geometry and Drawing objects we saw earlier in the chapter. Example 13-59. A custom OnRender implementation class MyFramedTextRenderer : FrameworkElement { protected override void OnRender(DrawingContext drawingContext) { Debug.WriteLine("OnRender"); drawingContext.DrawRectangle(Brushes.Red, null, new Rect(0, 0, 100, 50)); FormattedText text = new FormattedText("Hello, world", CultureInfo.CurrentUICulture, FlowDirection.LeftToRight, new Typeface("Verdana"), 24, Brushes.Black); drawingContext.DrawText(text, new Point(3, 3)); } } Table 13-9. DrawingContext drawing operations Operation Usage DrawDrawing Draws a Drawing object. DrawEllipse Draws an ellipse. DrawGeometry Draws any Geometry object. DrawGlyphRun Draws a series of glyphs (i.e., text elements) offering detailed control over typography. DrawImage Draws a bitmap image. DrawLine Draws a line (a single segment). DrawRectangle Draws a rectangle. DrawRoundedRectangle Draws a rectangle with rounded corners. DrawText Draws text. DrawVideo Draws a rectangular region that can display video. PushTransform Sets a transform that will be applied to all subsequent drawing operations until Pop is called; if a transform is already in place, the net effect will be the combination of all the transforms currently pushed. PushClip Sets a clip region that will be applied to all subsequent drawing operations until Pop is called; as with PushTransform, multiple active clip regions will combine with one another. PushEffect Applies a BitmapEffect to all subsequent drawing operations until Pop is called; as with transforms and clips, multiple calls to this method will combine effects. Visual Layer Programming | 465 Because our custom element derives from FrameworkElement, it integrates naturally into any WPF application. Example 13-60 shows markup for a window that uses this custom element—we can use it just like we’d use any custom element. Figure 13-62 shows this window. Notice that the OnRender function in Example 13-59 calls Debug.WriteLine. If the pro- gram is run inside a debugger, this will print a message to the debugger output win- dow each time OnRender is called. This enables us to see how often WPF asks our custom visual to render itself. If you are accustomed to how the standard on-demand painting in Win32 and Windows Forms works, you might expect to see this called regularly whenever the window is resized, or partially obscured and uncovered. In fact, it is called just once! It turns out that on-demand rendering is not as similar to old-style Win32 rendering as you might think. WPF will call your OnRender function when it needs to know what content your visual displays, but the way graphics acceleration works in WPF PushOpacity Sets a level of opacity that will be applied to all subsequent drawing operations until Pop is called; as with transforms and clips, multiple opacities are combined. Pop Removes the transform, clip region, or opacity added most recently by PushTransform, PushClip,orPushOpacity. If those methods have been called multiple times, calls to Pop remove their effects in reverse order. (The transforms, clip regions, and opacities behave like a stack.) Example 13-60. Loading a custom visual into a window Figure 13-62. Visual layer rendering in action Table 13-9. DrawingContext drawing operations (continued) Operation Usage 466 | Chapter 13: Graphics means that this happens far less often than the equivalent repaints in Win32. WPF caches the rendering instructions. (This rendering style is sometimes referred to as retainedmode , whereas the Win32 style is immediate mode.) The extent and form of this caching are not documented, but caching clearly occurs. Moreover, it is subtler than simple bitmap-based caching. We can add this code to the host window in Example 13-60 (this would go in the code-behind file): protected override void OnMouseLeftButtonDown(MouseButtonEventArgs e) { customRender.RenderTransform = new ScaleTransform(6, 6); } This applies a transform to our element, scaling it up by a factor of 6. When clicking on the user interface, the custom visual expands as you would expect, and yet OnRender is not called. Moreover, the enlarged visual does not show any of the pixelation or blur- ring artifacts you would see with a simple bitmap scale—it continues to be sharp, as you can see in Figure 13-63. This outcome indicates that WPF is retaining scalable information about the contents of the visual. It is able to redraw our visual’s on-screen appearance without bothering our OnRender method, even when the transformation has changed. This is in part due to the acceleration architecture, but also because transformation support is built into WPF at the most fundamental levels. WPF’s ability to redraw without calling OnRender allows the user interface to remain intact on-screen even if our application is busy. If the state of our object should change in a way that needs the appearance to be updated, we can call the InvalidateVisual method. This will cause WPF to call our OnRender method, allowing us to rebuild the appearance. Note that when you override OnRender, you would typically also override the MeasureOverride and ArrangeOverride methods. Otherwise, WPF’s layout system will have no idea how large your element is. The only reason we got away without doing this here is that we used the element on a Canvas, which doesn’t care how large its children are. To work in other panels, it is essential to let the layout system know your size. Example 13-61 shows custom rendering of text along with layout logic. Figure 13-63. Scaled custom rendering Where Are We? | 467 Chapter 3 described the MeasureOverride and ArrangeOverride methods in more detail. Example 13-61 defers to the FormattedText class to work out how much space is required. We describe FormattedText in the next chapter. Where Are We? WPF provides a range of high-quality rendering and composition services. A set of shape elements supports various drawing primitives. Several brush types are avail- able for determining how shapes are painted, and pens augment brushes to define how outlines are drawn. Transformability is supported at all levels, making it easy to scale a user interface to any resolution or size. And, a low-level API is available for working at the “visual” layer when necessary. Example 13-61. Custom rendering with layout logic class CustomTextRenderer : FrameworkElement { FormattedText text = new FormattedText("Hello, world", CultureInfo.CurrentUICulture, FlowDirection.LeftToRight, new Typeface("Verdana"), 24, Brushes.Black); protected override void OnRender(DrawingContext drawingContext) { drawingContext.DrawText(text, new Point(0, 0)); } protected override Size MeasureOverride(Size availableSize) { text.MaxTextWidth = availableSize.Width; text.MaxTextHeight = availableSize.Height; return new Size(text.Width, text.Height); } protected override Size ArrangeOverride(Size finalSize) { text.MaxTextWidth = finalSize.Width; text.MaxTextHeight = finalSize.Height; return finalSize; } } 468 Chapter 14CHAPTER 14 Text and Flow Documents 14 Text in WPF applications need never be plain. Any place a user interface displays text, all of WPF’s text rendering features are available. Basictext formatting is offered, including word wrapping, text alignment, and any mixture of fonts and text styles. Nontextual UI elements such as controls or graphics may be intermingled with text. ClearType text rendering is used on flat-panel screens, significantly enhancing the clarity, shape, and readability of characters. The various typography features available in OpenType fonts can be exploited. And, as you would expect, there is full support for international applications. As well as enabling fine control over textual details, WPF also defines types that rep- resent documents. It offers two kinds: fixed documents and flow documents. Fixed documents have a fixed layout and size, and are often created in order to be printed. These are described in Chapter 15. Flow documents are more flexible—instead of prescribing a particular layout, they are formatted dynamically to fit the space avail- able. This makes them ideal for presenting text on-screen, because most applications cannot know in advance the exact dimensions of the end user’s screen, or whether the user will resize her window. In this chapter, we will explore the options for presenting text, and we will examine the object model used by flow documents. Fonts and Text Styles You can work with text at several levels in WPF, but regardless of which level you choose, there are some common types and properties that control features—such as typeface, font weight, underlines, and so on—that you should know about. Because these types and properties crop up throughout the API, we will look at them first. Fonts and Text Styles | 469 Common Text Properties WPF defines a class called TextElement. This is part of the text object model used to define the structure and appearance of text, which we describe later in this chapter, but TextElement also defines a set of attached properties for formatting text. Many of these are inherited properties, meaning that if you apply them to some containing element, such as the Button in Example 14-1, the setting applies to any text inside that element. For example, if you were to apply these properties to a Window element, they would affect all text inside the window. As Figure 14-1 shows, the font settings applied to the button have had an impact on the text element inside the button that provides the caption.* Although an inherited property applies to all of the children of the ele- ment to which it is applied, it can be overruled. Obviously, giving the property a different value on a child element overrules the inherited value. More subtly, a property setter in a style will overrule an inher- ited property value, even if the style is picked up implicitly. A few controls have default styles that set common TextElement prop- erties, which results in inconsistencies if you set these properties on a Window. One example is Menu, which ignores the inherited font family and font size properties because its style sets the font to the system’s configured font menu. It does this because users are allowed to change this font with the Windows Control Panel. StatusBar and ToolTip also set the font in their styles for the same reason. Consequently, these elements will ignore window-level font settings. Table 14-1 lists the inherited attached properties defined by TextElement. Example 14-1. Using common TextElement attached properties * Recall that the default template for a Button includes a ContentPresenter to host the content. If the content is plain text, a ContentPresenter will generate a TextBlock to display that text. This TextBlock inherits the two TextElement properties set on the Button. Figure 14-1. TextElement properties applied to descendents of a button 470 | Chapter 14: Text and Flow Documents Because the need to control text properties crops up so often, several elements pro- vide aliases for these properties. For instance, we can rewrite Example 14-1 as shown in Example 14-2. The two examples are exactly equivalent—we are setting the FontFamily and FontSize properties defined by TextElement in both cases. The Control. FontFamilyProperty field refers to the very same DependencyProperty object as the field of the same name in TextElement. The Control, AccessText, and TextBlock classes all provide aliases for the properties in Table 14-1. Page provides aliases for FontFamily, FontSize, and Foreground. The following sections describe these properties and the associated types. Fonts and Font Families The common FontFamily property’s type is FontFamily. This is one of the three classes WPF offers for working with fonts, which are listed in Table 14-2. Table 14-1. TextElement inherited properties Property Usage FontFamily Typeface family (e.g., Palatino Linotype, or Arial). FontSize Font size in device-independent pixels. (XAML can specify alternative units with a suffix: in, cm, px, and pt indicate inches, centimeters, pixels, and points, respectively. These are all converted at compile-time to a numeric value in pixel units.) FontStretch A value from FontStretches, such as Condensed, Normal or Expanded. FontStyle A value from FontStyles, such as Italic or Normal. FontWeight A value from FontWeights, such as Normal, Bold, or Light. Foreground Brush with which text is painted. Example 14-2. Using text format property aliases Table 14-2. WPF font classes Class Usage FontFamily Represents a named family of fonts such as Arial, Times New Roman, or Palatino Linotype. GlyphTypeface Wraps a specific font file on disk, such as C:\Windows\Fonts\timesbi.ttf. This will contain a partic- ular weight and style of a font family, such as the bold, italic version of Times New Roman. Typeface Encapsulates a FontFamily, weight (e.g., bold), style (e.g., italic), and stretch (e.g., con- densed). This is just a description, and unlike GlyphTypeface, you can create a Typeface representing a font that is not present on your system (e.g., one used by a document created on some other machine that has the font). Fonts and Text Styles | 471 FontFamily just identifies a named family of fonts, rather than any particular weight or style. Example 14-1 used this to select the Parchment font family. In order to ren- der text, WPF needs more than this—most font families have variants such as bold and italic. For elements using the common text properties in Table 14-1, these facets are managed by separate properties. This is useful because it lets you control fea- tures such as weight and italics independently—if you just choose a font weight of bold without specifying a font family, the element will inherit the family from its parent. Property inheritance does not apply automatically to the low-level text handling APIs. These offer very fine control, but they require the font to be specified comprehensively and explicitly. This is why WPF offers the other two types, Typeface and GlyphTypeface. We will illustrate their use later when we look at the visual-level text APIs. You should not count on all variants of a font being available for any particular font family. For example, some fonts do not have bold versions, or may be available only in bold. You can discover which variants are available by retrieving the list of Typeface objects from a FontFamily object’s GetTypefaces method. FontSize The FontSize property is of type Double. It specifies the font’s size in pixels. If you’re working in XAML, you can specify other units by adding a suffix, as Example 14-3 shows. Stretch The FontStretch property lets you choose a condensed or expanded variant of the typeface. It accepts any of the values provided by the FontStretches class. These are: UltraCondensed, ExtraCondensed, Condensed, SemiCondensed, Normal, Medium, SemiExpanded, Expanded, ExtraExpanded, and UltraExpanded. Stretched variants of fonts are not created simply by scaling. Doubling or halving the width of a character would distort it, changing the width of horizontal features with- out changing vertical aspects to match. Each stretch type requires a separate font file. A font family will not normally offer all of the stretch types listed previously. Most fonts offer either exactly one (usually Normal), or a handful. For example, the Gill Sans MT font that ships with various versions of Microsoft Office comes in Normal, Condensed, Example 14-3. FontSize units 472 | Chapter 14: Text and Flow Documents and ExtraCondensed, but the latter is available only in bold.* If the font family does not offer the stretch you request, WPF will choose the nearest matching stretch. Style The FontStyle property indicates whether a font should be upright or slanted. This can be any of the three values from the FontStyles class: Normal, Italic,orOblique. The distinction between italic and oblique is that an italic font is a distinct font, where the character shapes are typically different from (but harmonious with) the normal version, and are defined in a separate file. An oblique style is formed by skewing a normal font—it does not require a separate font file, as it just transforms the shapes of an existing font. Figure 14-2 shows the Palatino Linotype font family in all three styles. As you can see, the italic font has letters that are of a significantly different shape from the equivalent characters in the normal style. This is partly because italic fonts typically have a slightly more decorative style, but also because simply skewing the character shapes—which is what an oblique font does—produces rather low-quality results, distorting the letter shapes. You would normally use an oblique font only as a fallback when an italic font is missing for some reason. Weight The FontWeight property determines how dark the text appears. You specify one of the values from the FontWeights class. Where two values are listed in the same table entry, it means they are different names for the same weight—for historical reason- scertain weights go by more than one name. The available FontWeights are Thin, ExtraLight/UltraLight, Light, Normal/Regular, Medium, DemiBold/SemiBold, Bold, ExtraBold/UltraBold, Black/Heavy, and ExtraBlack/UltraHeavy. As with FontStretch, most font families do not offer variants for every weight. WPF will choose the nearest match. It has no facility for adjusting character shapes to * Just to confuse matters, Office also provides a font called Gill Sans, which is a different family—note the missing “MT.” This offers only Normal and Condensed, and only in ultra-bold. Figure 14-2. Normal, Italic, and Oblique Fonts and Text Styles | 473 “lighten” or “embolden” text—each font weight requires a font file defining the character shapes for that weight. The properties discussed so far in this section are all defined by the TextElement class. There are a few properties that are widely used by WPF’s text facilities, but which are defined by other more specialized types because they apply only to certain types of textual element—text alignment makes sense for paragraphs or blocks of text, but not for an individual word, for example. The following sections examine these properties. Decorations A decoration is a line drawn through a piece of text, such as an underline or strikethrough. The Inline class, which is part of the text object model described later in this chapter, defines an attached TextDecorations property, which is aliased by both AccessText and TextBlock. This property supports the four decoration styles shown in Example 14-4. Figure 14-3 shows the results. As you can see from the final item, it is possible to use multiple decorations. This is because the TextDecorations property is of type TextDecorationCollection. The syntax shown in Example 14-4 is easy to use but slightly limited. If you create the TextDecoration elements explicitly, you can control the pen used to paint the decoration, and its exact vertical position. Example 14-5 sets two decorations on an element: a blue underline and a thicker green strikethrough. Example 14-4. Text decorations Underline, Baseline, Strikethrough, Overline, Full house Figure 14-3. Text decorations 474 | Chapter 14: Text and Flow Documents Figure 14-4 shows the results. The shorter syntax shown in Example 14-4 is available only in XAML—it’s provided by a type converter class. However, WPF makes it just as easy to set simple decorations from code. It provides the TextDecorations class, which offers static properties contain- ing text decoration collections holding exactly one decoration, such as an underline or a strikethrough. Example 14-6 uses this to apply a simple underline decoration. As this example shows, the staticproperties offered by TextDecorations make it as simple to set a single decoration from code as it is from XAML. Text Trimming If you try to display more text than fits in the space available, something has to give. Some of the text viewing elements described later in this chapter deal with this by scrolling or paging through the text. However, the TextBlock and AccessText ele- ments both simply crop the text. They each offer a TextTrimming property, shown in Example 14-7, which takes a value from the TextTrimming enumeration, allowing the cropping behavior to be modified. Example 14-5. Setting text decorations Highly decorated Figure 14-4. Underline and strikethrough decorations (Color Plate 26) Example 14-6. Simple underline decoration text.TextDecorations = TextDecorations.Underline; Example 14-7. TextTrimming Fonts and Text Styles | 475 The effect of the None setting (which is the default) is shown in Figure 14-5—the text has been cut off mid-character. A black border has been added to the edges of the figures in this section to illustrate where cropping occurs relative to the available space, and with this setting, the whole space is used. Figure 14-6 shows one of the other two options: CharacterEllipsis. This crops to an exact number of characters. It also adds an ellipsis to indicate that cropping has occurred, which has the side effect of reducing the number of visible characters. It also means that the space available is not filled completely—with this setting, WPF cannot show a partial character in order to fill the space, as it did in Figure 14-5. The final option is WordEllipsis, which crops at a word boundary. As Figure 14-7 shows, this can reduce the amount of text that is shown further still, particularly when only a handful of words will fit. WPF has had to cut the text off after the first word because where wasn’t room to fit both much and an ellipsis, resulting in a lot of unused space. However, even though this is the least space-efficient option, it can sometimes lead to less confusing results—cropping text at a word boundary reduces the chances of changing the apparent meaning of the text. Text Wrapping and Hyphenation Often, a UI layout will have insufficient horizontal width to show some text, but spare vertical space. Not all text elements will exploit this space by default. Figure 14-8 shows a traditional English tongue twister displayed by a TextBlock, and as you can see, it has failed to use the available vertical space. Figure 14-5. TextTrimming.None Figure 14-6. TextTrimming.CharacterEllipsis Figure 14-7. TextTrimming.WordEllipsis 476 | Chapter 14: Text and Flow Documents To use the vertical space, we must enable text wrapping. Both TextBlock and AccessText offer a TextWrapping property, which takes a value from the TextWrapping enumeration. This defaults to NoWrap, but Figure 14-9 shows the effect of setting it to Wrap. The TextWrapping enumeration offers a third value: WrapWithOverflow. The distinc- tion between the two wrapping styles is in the way they deal with individual words that are longer than the available space. Figure 14-10 shows a piece of text with this problem. The left of Figure 14-10 shows how Wrap deals with this—it simply breaks the word across multiple lines. On the right, we see the WrapWithOverflow behavior: over-long words are cropped. A more elegant solution to this problem is commonly used in print: hyphenation. Splitting words with hyphens can enable word wrapping to work better in confined spaces. The Block class, which is part of the text object model described later in this Figure 14-8. Vertical space not used by default Figure 14-9. Word wrapping Figure 14-10. Wrap (left) and WrapWithOverflow (right) Fonts and Text Styles | 477 chapter, defines an attached IsHyphenationEnabled property, and TextBlock provides an alias for this, as Example 14-8 shows. As Figure 14-11 shows, hyphenation enables the text to fit on fewer lines than in Figure 14-10, and with less compromise. Because hyphenation seems to be better in every respect, it may seem strange that it is disabled by default. However, the hyphenation algorithm is complex, and there are nontrivial costs to enabling it. Because hyphenation is appropriate only for certain scenarios—presenting bodies of text in relatively narrow spaces—it makes sense for it to be off by default. Hyphenation is a language-specific process. WPF takes the element tree’s language into account for both hyphenation and spellchecking. In XAML, you can set the language using the standard xml:lang attribute—you can set this to any culture string, such as en-GB or fr- CA, which represent British English and French Canadian, respec- tively. From code, you can set the Language property of any FrameworkElement or FrameworkContentElement object. Text editing con- trols with the SpellCheck.IsEnabled attached property set to True also honor the language setting. Hyphenation and spelling dictionaries are shipped as part of the .NET 3.0 language packs provided by Microsoft. At the time of this writing, dictionaries are provided for English, German, French, and Spanish. Text Alignment TextAlignment is an attached property defined by the Block class. The property accepts any value from the TextAlignment enumeration type. This offers four values, all of which will be familiar to you if you’ve ever used a word processor: Left, Right, Center, and Justify. Figure 14-12 shows the effect of the Justify setting. As you can see, the righthand edge is now flush with the available space, as opposed to the ragged-right edge shown in Figure 14-9. Example 14-8. Enabling hyphenation A cumbersome word. Figure 14-11. Hyphenation 478 | Chapter 14: Text and Flow Documents We’ve looked at the mechanisms available for describing how text should be format- ted. However, a description of formatted text isn’t much use unless we can some- how display that text, so it is time to look at the elements available to us for presenting text in a user interface. Text and the User Interface As we saw in the Introduction and Chapter 13, a WPF application’s appearance is defined by its visual tree—a tree of objects derived from the Visual base class. Text must fit into this model, but we have several different options for adding text into the visual tree depending on the balance we require between control and ease of use. The lowest level at which we can work with text is to use the visual layer drawing techniques introduced in Chapter 13. The next level up is to use the Glyphs class, which offers a similar level of control as visual layer programming, but packaged into a framework element. This enables it to be used from markup and to provide the usual framework-level features, such as event support and participation in layout. The GlyphRunDrawing class offers similar features, but you can incorporate it into a drawing. Finally, you can use the text object model in conjunction with one of the elements that knows how to render this form of text, such as TextBlock or FlowDocumentReader. TextBlock is the most widely used, as it offers a good balance between simplicity and flexibility. TextBlock The TextBlock element is usually the best choice for presenting simple text. It can handle both plain text and formatted text, and can cope easily with anything from a single character to a few paragraphs. Example 14-9 shows TextBlock at its simplest. Figure 14-12. Justified text Example 14-9. Simple TextBlock Text and the User Interface | 479 Because TextBlock derives from FrameworkElement, its position and size will be deter- mined by WPF’s normal layout mechanisms (which we described in Chapter 3). A TextBlock can span multiple lines. A straightforward though inflexible way to do this is to put either a carriage return or a linefeed (character values 13 and 10) or both* into the Text property. Example 14-10 shows this technique. Note that because all three of the popular representations for a new line are treated equivalently, there’s no need to use .NET’s Environment.NewLine property in WPF. Although this hardcoded line break works fine, a better solution might be to switch on word wrapping, enabling the TextBlock to choose where to put line breaks. This is controlled with the TextWrapping property, one of the common text properties, described earlier in this chapter, in the “Fonts and Text Styles” section. TextBlock defines aliases for all of these common properties. As an alternative to setting the Text property of a TextBlock, you can supply content inside the element. Example 14-11 uses this technique to create the same result as Example 14-9. Moving the text inside the element doesn’t add anything very useful for this particu- lar example. However, by representing the text as content, we can go beyond plain strings, as Example 14-12 shows. As Figure 14-13 shows, this renders the word “Some” in bold, and the word “text” with the normal font weight. * A carriage return followed directly by a line feed is treated as a single new line. This is the most common convention for representing line ends in Windows. It originates from the days when computer terminals had a keyboard and printer but no screen. The printer used separate control characters to feed a new line of paper (10), and to return the print head to the start of the line (13). This character sequence is completely redun- dant today, but it is still the norm in Windows thanks to backward compatibility and developer inertia. Example 14-10. Multiline Text value Example 14-11. Text as content Some text Example 14-12. Text with mixed content Some text Figure 14-13. Mixed bold and ordinary text 480 | Chapter 14: Text and Flow Documents When you add content to a TextBlock in this way, you are adding items to its Inlines property. This is a collection of objects, all derived from Inline. This class and its derivatives (such as Bold and Italic) are described in detail in the “Text Object Model” section, later in this chapter. For now, it is enough to think of an Inline as some formatted text contained within a single paragraph. This means that a good way to think of TextBlock is as a framework element that renders a sequence of Inline text elements. Label and AccessText WPF defines a Label control, which is also able to display text. If you have used Win- dows Forms, Label might look like the obvious choice for displaying text, because Windows Forms also defined a Label control, which was used for displaying simple text. However, the purpose of WPF’s Label control is different. As mentioned in Chapter 5, WPF’s Label control’s purpose is to place the focus into another control such as a TextBox when an access key is pressed. Using a Label con- trol simply as a plain-text label is wasteful—it creates a TextBlock internally to ren- der plain text for you, so you might as well just create the TextBlock yourself. Label has one other trick: you can use it to add underlines for access keys when the user presses the Alt key. As Example 14-13 shows, you denote the access key with an underscore. When the user presses the Alt key (or if he has configured Windows to show access key underlines at all times), the relevant letter will be underlined, as shown in Figure 14-14. However, if the only reason you’re using Label is to show an access key underline, there’s a better alternative. When you provide Label (or any control that supports the content model) with a string containing an underscore, it generates an AccessText element to present that string instead of a TextBlock. So, it would be more efficient to use this directly, as Example 14-14 does. Example 14-13. Access key underline with Label