|
|||||||||||||||||||||||
|
|||||||||||||||||||||||
|
Announcements
Chapters
Services
Feature Zones
|
Note: This is an unedited contribution. If this article is inappropriate,
needs attention or copies someone else's work without reference then please
Report This Article
IntroductionMany applications feature some kind of "message bar", to display error, warning, status, or other categories of messages to the user in an aesthetically pleasing and unobtrusive way. While the basic functionality is trivial to implement in WPF (just stretch a TextBlock accross the top of your Window), in a production environment, you might need to create something which can be shared accross projects, featuring subtle animation effects, supporting a simple Xaml instantiation syntax... The attached project implements an "AlarmBar" custom control supporting "Glass" and "Etched" visual styles and several animation options. Messages slowly fade-in, remain visible for several seconds, and then fade away (in a real-world application, supporting details would be written out separately, to a log of some kind).
Adding an AlarmBar, and customizing the bar's appearance and behavior, requires just a few lines of Xaml, and you can define as many Alarm categories, with associated custom color schemes, as your application needs. Trying to add a Button, or other invalid child, to the collection, will trigger an immediate (design time) error notification.
To display an Alarm, you simply specify an Alarm id and message text. alarmBar.Display( “Error”, “Containment breach imminent. Run for your lives!” )
WPF Custom Control Project StructurePublished WPF resources discussing control customization focus almost exclusively on editing local copies of ControlTemplates, while implementing and interacting with an actual Custom Control library requires a DIFFERENT set of techniques and reference syntax to be used. If this is your first exposure to WPF Custom Controls, you will probably have a much easier time understanding the project code if you create an empty WPF client project and walk through the following steps. Note that a generic Custom Control will be generated in Themes/generic.xaml, and you must NOT rename or move that file.
Validating DESIGN-TIME Xaml EntriesTo display a message, the AlarmBar references information associated with one of the Alarm objects in its collection. The first decision you face in building a control like this is in choosing a base class to extend, in order to support adding Alarm objects (and ONLY Alarm objects) to your control via Xaml. Deriving from ItemsControl would enable adding collection members through mark-up, but you would not be able to validate additions at design-time (You could catch errors at RUN time by manually iterating over members and checking their type, but that’s not a very satisfying work-around). By deriving from ContentControl, and specifying Alarm to be the supported collection type, we cause an error to be generated automatically, at DESIGN-TIME, if the user tries to add an invalid child to our collection. Note that, although an AlarmBar contains a list of Alarms, we would probably not want to use a ListBox as our base class, since we are never actually displaying a list (technically, we display properties associated with the single "active" list item).
Preferring Xaml to Procedural CodeThere is very little actual code involved in implementing our control. We simply define templates and styles containing display elements bound to a handful of DependencyProperties. The procedural code involved in displaying an Alarm consists of setting the “ActiveAlarm” property to the collection member whose id matches that contained in the display request, setting that Alarm object’s message text, and launching Storyboard animations. The critical components that make up an AlarmBar’s default template are:
The appearance and behavior of the secondary animation and static overlay elements are defined in separate styles and templates, which are “inserted” into the AlarmBar, based on “VisualStyle” and “AnimationStyle” enum values (which are translated internally as described in the next section). Hiding ComponentResourceKeys From the ClientThere are two ways (ignoring reflection etc. techniques) for client code to reference WPF logical resources located in an external assembly. In the case of a simple resource DLL, we can specify the path to the remote Xaml file, <ResourceDictionary Source="/SomeAssembly;component/SomeResourceDictionary.xaml" /> and reference its resources using the same syntax we use locally. MyProperty="{StaticResource SomeResourceIdString}" Custom Controls often rely on resources identified in the context of a specific CLASS, using ComponentResourceKeys, which must be referenced from the client using one of the following syntax variations. MyProperty="{DynamicResource {ComponentResourceKey TypeInTargetAssembly={x:Type custom:SomeClass}, ResourceId= SomeResourceIdString }}" MyProperty="{DynamicResource {ComponentResourceKey {x:Type custom:SomeClass}, SomeResourceIdString }}" MyProperty="{StaticResource {x:Static custom:SomeClass.PropertyToExposeSomeResource}}" This requires clients to have knowledge of specific ComponentResourceKey strings defined inside the control library, which may not always be desirable. Our control avoids this by exposing properties backed by Intellisense-friendly enum values to the client, and translating those enums internally to ComponentResourceKeys, before setting the underlying DependencyProperties. To eliminate an extra step, we use identical strings to define the enum value and its associated ComponentResourceKey. MiscellaneousXaml file organization: An actual Custom Control Library will eventually contain many different controls, and dumping all of their associated mark-up into a single Animation Storyboards: Storyboards are “frozen” the instant they start running, causing attempts to bind the “To” or “From” properties of an animation to a dynamically-updated value, to fail, which is why we are forced to use the static The expected architectural approach to launching an animation would be for the AlarmBar to raise an "AlarmChanged" RoutedEvent, handled in the template via an EventTrigger. While that works well when running a SINGLE animation, I was unable to get it to work in our situation, where we sometimes have two animations that must run in parallel in the context of two different Templates.
|
||||||||||||||||||||||
|
PermaLink |
Privacy |
Terms of Use
Last Updated: 22 Jul 2008 Editor: |
Copyright 2008 by AndyL2 Everything else Copyright © CodeProject, 1999-2008 Web07 | Advertise on the Code Project |