DevComponents recently released their DotNetBar Suite for WPF and it is loaded with design time features. Using Cider Extensibility, DevComponents really has put together one of the richest design times I've seen to date.
Denis talks about the features on this blog post.
The DotNetBar suite provides a set of controls that make it easy to build an application with an Office Ribbon, Window Management and an Outlook style Navigation Pane control.
The first thing to notice is that for the elements you care to move to other positions -- the buttons in the Ribbon (including on the application menu), and the windows in the application -- can be moved by simply clicking and dragging. Very cool use of setting up a ParentAdapter correctly.
Here's a sequence where I moved a button from the Recent Documents list to the Command Button area to the Application Menu main buttons to a location on the ribbon -- all using drag drop.
For redocking the windows at design time, the user gets the Visual Studio-esque docking targets and highlighting:
Much better than having to find the right set of XAML to modify right?
Each selectable element also has a popup menu associated with it that allows the user to edit the controls in place.
The application Menu:
Drop down buttons (these can be dragged dropped around the whole UI to multiple drop targets):
The Ribbon Tab:
And Windows:
DevComponents have made use of the following Cider Extensibility features to make this happen: Adorners, DefaultInitializers (to setup the XAML for their controls when they are created from the toolbox), and ParentAdapter (for drag/drop move/repositioning of theirr controls).
Really is a great example of how powerful the Adorner Extensibility features are in Cider.
Again -- I am looking for more design time examples to feature on this blog so please contact me with your examples!
I've received two bug reports from Control Vendors regarding Style.Triggers not working correctly at design time in the WPF Designer RTM. Fortunately, we do have a workaround to the problem. Suppose you have a trigger defined as follows:
<Style TargetType="{x:Type local:CustomControl1}"> <Style.Triggers> <Trigger Property="local:CustomControl1.SecondTemplate" Value="True"> <Setter Property="Template"> <Setter.Value> <ControlTemplate TargetType="{x:Type local:CustomControl1}"> <Border Background="Red" BorderBrush="Green" BorderThickness="4"> </Border> </ControlTemplate> </Setter.Value> </Setter> </Trigger> </Style.Triggers> <Setter Property="Template"> <Setter.Value> <ControlTemplate TargetType="{x:Type local:CustomControl1}"> <Border Background="{TemplateBinding Background}" BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}"> </Border> </ControlTemplate> </Setter.Value> </Setter> </Style>
What you would expect is that in the designer when you set the SecondTemplate property to true you would get something that looks like this:
Unfortunately, the style doesn't update.
To work around this issue, if you define triggers for both the true and false values, you will get the desired behavior:
<Style TargetType="{x:Type local:CustomControl1}"> <Style.Triggers> <Trigger Property="local:CustomControl1.SecondTemplate" Value="True"> <Setter Property="Template"> <Setter.Value> <ControlTemplate TargetType="{x:Type local:CustomControl1}"> <Border Background="Red" BorderBrush="Green" BorderThickness="4"> </Border> </ControlTemplate> </Setter.Value> </Setter> </Trigger> <Trigger Property="local:CustomControl1.SecondTemplate" Value="false"> <Setter Property="Template"> <Setter.Value> <ControlTemplate TargetType="{x:Type local:CustomControl1}"> <Border Background="{TemplateBinding Background}" BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}"> </Border> </ControlTemplate> </Setter.Value> </Setter> </Trigger> </Style.Triggers> </Style>
Hope that helps!
This sample is attached. When you open the sample it will be in a state where the style triggers don't, well, trigger at design time. If you uncomment the workaround and comment the original style, you will see the style triggers will then work in the designer.
There are definitely some cool 3rd party WPF tools coming out as of late -- for example, Mole. Here is another cool one that helps you to get a handle on your WPF resources.
http://www.granthinkson.com/2007/11/08/announcing-pistachio-wpf-resource-visualizer/
Another great example of cool design time functionality in Cider (WPF Designer in Visual Studio 2008) comes from Actipro. Their Office Ribbon control uses Cider Extensibility to add task panes invoked by an adorner that makes configuring their control a breeze.
For example, when the main Ribbon is selected a little widget adorner in the upper right hand corner is shown and when clicked will bring up a task pane that uses the Cider Editing Model to make updates to the XAML:
Likewise, they've done that for TabGroup, Tab and StackPanel tasks:
There are other examples as well, virtually every major clickable element has a task pane associated with it. Very cool.
You can also drag and drop elements of your Ribbon around -- which really makes designing your control really easy.
Actipro has also made good use of the Property Browser, the collection editors have all been setup using the NewItemTypesAttribute which allows the end user of their controls to use the collection editors to configure the Ribbon:
Similarly, with non collection properties in the Property Browser:
Actipro uses the following Cider Extensibility points: DesignModeValueProvider (set a different value for the instance in the UI from that which is serialized to XAML), lots of Adorners, and Property Editing Extensibility.
For additional information, see http://www.actiprosoftware.com/Products/DotNet/WPF/Ribbon/DesignerSupport.aspx
Now that Visual Studio 2008 has been released, one of the real fruits of my job is to see how our customers end up using our product. Specifically, I really enjoy seeing how WPF Control Developers end up using the WPF Designer Extensibility API that I talk about so much on this blog.
If you have examples of how you are using extensibility for your WPF controls -- please drop me a line (you can send a message from this blog) and I'll definitely blog about your product.
The first example I received that shows off design time extensibility comes from divelements - SandDock for WPF.
SanDock is a very cool set of controls that provide a vast array of Window Management functionality. Here is a shot of their demo application inside the designer. (showing off how you can redock your windows at design time using adorners)
Here they use MenuActions to add context menu items that help the user configure their application:
Finally, here is a more clear shot of their clever use of adorners to provide a design time window docking functionality similar to the dockable windows feature in Visual Studio:
They have also made their own EverythingPolicy (to show adorners on more elements than just the primary selection), ParentAdapter (to handle reparenting), and DefaultInitializer (note the XAML spit when instantiating their controls from the toolbox),
Lots of great illustrations of how you can use the power of Cider's extensibility model to create a first class design time experience.
Here is the divelements page describing the designer support: http://www.divelements.co.uk/net/controls/sanddockwpf/documentation/designer.htm
If you add a TextBox as an adorner to your design time, by default you will not be able to give that TextBox focus. That is, you won't be able to set the cursor in it so that you can type.
Similarly, if you use a TabControl as an adorner, by default you won't be able to switch the tabs by clicking on them.
The solution is to set the AdornerPanel.IsContentFocusable property to true:
AdornerPanel ap = new AdornerPanel(); ap.IsContentFocusable = true;
One caveat here: we know we have a bug in the TextBox keyboard handling where the delete key will always delete the selected control, even when the user is in text entry mode in a TextBox -- i.e. hitting the delete key won't delete the text, it will delete the control.
Cider is currently known as the "WPF Designer for Visual Studio" but we're also in the works of being known as the "Silverlight Designer for Visual Studio".
In other words, we care about Silverlight and the following is pretty cool:
· On December 19th, Blockbuster will premiere the first full-length feature film, Jackass 2.5, directly to online audiences on Windows and Mac using Microsoft Silverlight
· Jackass 2.5 will be made available for free, for anyone in the US 17 or older, courtesy of Blockbuster at www.blockbuster.jackassworld.com beginning December 19th through December 31st, 2007
· This is one of the largest projects in Blockbuster history, made possible using the rich media CDN from Limelight Networks and Silverlight
· Silverlight is a 1.5MB cross-browser, cross-platform plugin for IE, Firefox and Safari users. Details and download at http://www.microsoft.com/silverlight
The ecosystem around Silverlight is continuing to grow, with an impressive line-up of customers like MLB.com, BMW, Sony Ericsson, Baidu (#1 search site in PRC), NBA.com, Entertainment Tonight (CBS/Paramount) and UVNTV.com, to name a few. See more at http://silverlight.net/Showcase/
Recently I had a customer point out the following from the documentation:
Differences in Specifying Toolbox icons
In the Windows Forms Designer framework, you specify a Toolbox icon for your custom control by applying the ToolboxBitmapAttribute to your control class.
In the WPF Designer framework, you use an embedded resource and a naming convention to specify a Toolbox bitmap. In addition, you use the ToolboxBrowsableAttribute to restrict which types in an assembly are available for populating a Toolbox.
Followed by the question "What is the naming convention?"
I thought I'd answer that question by posting to my blog as it is not the first time I've had that question.
First, a note about the ToolboxBrowsableAttribute, it is defined in Microsoft.Windows.Design.dll so you don't want to use it declaratively in your code because that would require making a reference to Microsoft.Windows.Design.dll which will only be available on machines that have Visual Studio 2008 installed. Use the Metadata Store as discussed here and Metadata Assemblies as discussed here.
Toolbox Icon Naming Convention
Example:
Type is defined as:
namespace Proseware.Core.Controls
{
public partial class ProseControl : UserControl
public ProseControl()
InitializeComponent();
}
Default namespace: Proseware.Core.Controls
ProseControl.Icon.png is added as an Embedded Resource.
This causes the image at Proseware.Core.Controls.ProseControl.Icon.png from the resources inside the assembly in which ProseControl is contained to be used as the toolbox icon:
(view from reflector)
Multiple Sizes
The naming convention supports multiple image sizes. For the following example:
For the ProseControl type, the following images in the resources will all be found and the best match for size will be used.
Proseware.Core.Controls.ProseControl.Icon.Large.png
Proseware.Core.Controls.ProseControl.Icon.Medium.png
Proseware.Core.Controls.ProseControl.Icon.ReallyLarge.png
Or, alternatively (the ‘.’ After Icon is not required but is acceptable):
Proseware.Core.Controls.ProseControl.IconLarge.png
Proseware.Core.Controls.ProseControl.IconMedium.png
Proseware.Core.Controls.ProseControl.IconReallyLarge.png
If the desired size by the host is 64 pixels by 64 pixels, and Proseware.Core.Controls.ProseControl.Icon.Large.png is the best match based on size and aspect ratio, it will be used. All of the images will be looked at.
If there are 2 images with the same size and aspect ratio, the host will decide which it will use.
Sample Project
Attached to this post is a sample project that illustrates how to get an icon defined for your control. To test, build the solution, right click on the toolbox and click "Choose Items...". Switch to the WPF Components tab and navigate to one of the control assemblies. You will then see your control with it's icon on the toolbox.
Ran across this today and I thought I would pass it along as it is a very useful tool for WPF developers.
Mole II is a high performance, full featured Visual Studio Visualizer which allows you to inspect elements in the WPF visual tree or logical tree, as well as all properties of those elements.
http://karlshifflett.wordpress.com/mole-visual-studio-visualizer-for-wpf/
Mark Wilson-Thomas is our resident WPF Layout expert and is leveraging my blog to get some great information out about how the VS 2008 WPF Designer approaches layout:
How to get the best out of the WPF designer when laying out your WPF app
Lots of folks have asked what the reasoning is for the WPF designer giving a new control instance on the design surface a width and height, and margins so that it stays where you drop it and sizes to a reasonable default size. The story of “why we emit margins to make things feel like anchored controls in Windows Forms” by default in the Cider layout system is really about making it possible to traverse two paths to your final layout, and to cross over from one to the other:
Path 1: This is the popular “Visual Place-and-size” path that people have been used to on practically every GUI designer that's shipped since Visual Basic.
Path 2: This is the “Container first” path, where you lay out the Grid(s) or other containers that will govern the layout, then place controls inside them (generally with zero margin) and let the container take care of the resize behavior for you
Path 2 is an approach I’ve seen advocated in several posts and discussions on the topic. Both from a “WPF platform-purist” point of view and an experienced Web UI developers point of view, I agree this a clear and straightforward way of thinking about layout in WPF. However if you’re one of the many millions of developers who have been working with Windows Forms, VB, etc for years, and have never built a web app, it will probably not be an easy transition for you to be forced to start thinking this way for your first app building experiences – and in addition, you may never want to build a fully resizable app.
So if you’re a developer with that set of experience, what we let you do is get a pretty much functional WPF app in the traditional way you always have using path 1 (and actually for a lot of applications you might even stop there). If you wish to, we then give you help to start dropping gridlines into your design and snapping them to your controls/your controls to them (getting the zero margins you are looking for) ready to move into Auto-layout-land. Unfortunately, we didn’t have time on Cider v1 to deliver the full set of features that would give you the “flip-to-auto” part of the story on the design surface, so you have to go to XAML or the property inspector to make it happen, and when you do the behavior of the design surface can become a little “quirky” in places.
So, what’s the best path for getting great layout in the designer then?
The application layout path that works best right now if you want to use the WPF design surface is:
NOTE 1: If you need to move/resize things after this step, it’s best to flip back out of auto first.NOTE 2: Steps 1 & 2 are pretty much interchangeable
Why doesn’t the designer favor auto layouts by default?
I think it's reasonable to argue we should have started with a mode that favored auto layouts by placing new controls with no margins in whatever container they were added to (rather than the method we have used) by default. However after many discussions in the team we felt this kind of total break with the past for our default experience would be just too jarring for too many existing UI technology users.
I do think that a designer setting that allowed you to have a more "start with auto" approach would be a useful feature, and we'll think about that for future releases. As an aside, right now one way to work in a "start with auto" approach should you wish to do so, is to work entirely in the XAML editor and not use the toolbox for control creation at all. That way you will get only the properties you explicitly wanted at creation time.
We've been seeing some issues where VS 2008 crashes when creating a new WPF project. The underlying issue is that on certain machine configurations, the .Net Framework is not updating correctly.
In order to short circuit diagnosing that you're in this state, one of my colleagues on the deployment team has written a tool that will tell you whether or not the .Net Framework is installed correctly or not.
If you are having issues, please take a look at this tool http://blogs.msdn.com/aaronru/archive/2007/11/29/net-framework-3-5-installation-validation-tool.aspx. It will tell you whether or not your .Net Framework installation is correctly installed.
After that, have a look at this tool to collect log files: http://blogs.msdn.com/astebner/archive/2007/08/01/4172276.aspx
If you do have this issue, please contact the Cider team directly via our forum at: http://forums.microsoft.com/MSDN/ShowForum.aspx?ForumID=169&SiteID=1 or via my blog and have the results of the .Net Framework install check and log files ready.
Thank you and sorry for any inconveniences.
This is a pretty exciting day as Visual Studio 2008 has shipped. This is the first product I've shipped with Microsoft which makes it a milestone for me. I think back to what feels like yesterday (2 years ago) when I started on Project Cider and all we had was a half working XAML previewer.
Since I represented my team in the Developer Division shiproom, late last week I got to sign these big 6 foot Visual Studio boxes and received a t-shirt of all of the best shiproom quotes. I was happy to see I got quoted twice!
It really has been a great experience for me and I've learned a lot over the past two years. I really look forward to working on our next release.
Here is Soma's announcement:
One of the challenges of building a designer after the framework has shipped (as is the case with Cider) is that the designer isn't always able to support all of the coding patterns that develop before the tools become available.
This is not a new problem. Brian Pepin wrote a similar article to this one back in 2004 about Windows Forms.
This post will discuss some of those patterns that are valid at runtime but are not supported by Cider.
Code Behind in Controls
Cider, like Blend is fundamentally a XAML designer. We load up XAML files. When you write code behind for a particular control, that code behind may or may not be run at design time.
If the code behind is in the constructor of a control that is hosted by a parent in the Cider designer, it will be run when that control is loaded onto the design surface. For instance, if I create a UserControl named MyUserControl and place it on a Window, the constructor for MyUserControl will be run when that Window is loaded in Cider.
However, if I am designing MyUserControl in Cider, because that type is being modified and created at that time, we don't instantiate MyUserControl -- which in turn means that none of the code behind for MyUserControl will be run.
By the same logic, the code behind for any Window that is being designed will also never be run.
Additionally, if you ever load XAML that binds to custom properties for a type being designed (i.e. Window is being designed or MyUserControl is being designed), those bindings will fail because again, the instance that is on the designer is the base class (Window, UserControl) of the type you are designing and not the actual type.
For example:
<Window x:Class="DontDoThis.Window1" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Title="Window1" Height="300" Width="300" Name="MainWindow"> <Grid> <!--Notice how I can't bind to a Custom property on Window1 since the CustomInt property doesn't exist at design time--> <TextBox Text="{Binding ElementName=MainWindow, Path=CustomInt}" Height="29" Margin="55,28,77,0" VerticalAlignment="Top" /> </Grid></Window>
Base Classes
As I've alluded to above, when we design a type, we instantiate its base class and show that in the designer. What that means is that the base class for any type that you wish to show in the designer must be concrete (i.e. not abstract) and also have a public default constructor.
That is, if I derive a class AbstractDerivedWindow from Window and then derive ConcreteDerivedWindow from AbstractDerivedWindow and try to load up and design ConcreteDerivedWindow, we will fail since we try to instantiate AbstractDerivedWindow, which we cannot.
More concretely:
<loc:AbstractDerivedWindow x:Class="DontDoThis.ConcreteDerivedWindow" . . . > . . . </loc:AbstractDerivedWindow>
will not load in the designer with the error: Type 'AbstractDerivedWindow' is not usable as an object element because it is not public or does not define a public parameterless constructor or a type converter.
Setting Property Values in Code Behind
For compiled controls, for example a UserControl on a Window, that control is instantiated XAML + code which means that the code behind will run for that UserControl when it is on the design surface.
If the code behind for that UserControl makes property changes, our model will not pick that up... which will result in the model and the designer getting out of sync with each other.
In some situations, that isn't so bad, consider the following:
public partial class UserControl2 : UserControl { public UserControl2() { InitializeComponent(); Background = Brushes.Blue; }
Cider will look at the properties that are set on a control after it's constructor has run and treat those values as the default values regardless of whether or not they are truly the default value as per the Dependency Property definition, set by the XAML for that control (i.e. UserControl2.xaml) or set programmatically as above.
When I instantiate UserControl2 above on Window1.xaml, the Background property is set to Blue. If I set the Background property locally:
<loc:UserControl2 Background="Cyan" />
It changes as expected and when I delete the Background attribute in the XAML (<loc:UserControl2/>) the Background goes back to Blue.
That said, there are situations like the following where our designer will not match the runtime values because the properties are being set programmatically and our model doesn't pick it up.
public class CustomControl1 : Button { String property_One;
public String Property_One { get { return property_One; } set { SolidColorBrush backgroundSolidColorBrush = System.Windows.Media.Brushes.Firebrick; SolidColorBrush foregroundSolidColorBrush = System.Windows.Media.Brushes.White; Background = backgroundSolidColorBrush; Foreground = foregroundSolidColorBrush; property_One = value; } } }
And this control is instanced on a Window as follows:
<custom:CustomControl1 Margin="11,36,11,124" Property_One="One"/>
In this case, due to some internal "shadow property/design mode value provider" magic we do in the designer, the Background and Foreground properties are set to Firebrick and White respectively at runtime but not in the designer.
It's also important to note that the designer does not make calls to property accessor methods (get/set) for Dependency Properties.
Finally
Along the same lines as what I discuss here and some of my other "why doesn't my XAML load" posts, Jim Galysn has written a great post about troubleshooting designer load failures.
As it turns out, we have a bug in finding metadata assemblies that will be in Visual Studio 2008 RTM. I described how we find and load metadata assemblies in a previous post and the bug pertains to the situation where:
The main line scenarios where this will be true is:
Workaround
In order to handle all of the situations (note that your customers may GAC your assemblies on you provided your assemblies are strong named) you need to provide 2 sets of design time assemblies (in your AssemblyFolder):
Sorry for the inconvenience, at least there is a reasonable workaround.
Recently I made a post about how to deal with Application.Current at design time. I thought I'd follow that up with another example of why your XAML may not show up in the WPF Designer (Cider) due to assumptions that are not true at design time.
In this case, we're talking about implementing an IMultiValueConverter for a MultiBinding:
public class SampleConverter : IMultiValueConverter { public object Convert(object[] values, Type targetType, object parameter, System.Globalization.CultureInfo culture) { string text = (string)values[0]; Brush backgroundBrush = (Brush) values[1]; return text + backgroundBrush.ToString(); } (. . .) }
In my sample, I am using this SampleConverter in a ControlTemplate that is defined in Application.Resources:
<ControlTemplate x:Key="ReadOnlyTextBlockTemplate" TargetType="{x:Type TextBox}"> <ControlTemplate.Resources> <loc:SampleConverter x:Key="SampleConverter"/> </ControlTemplate.Resources> <Border BorderBrush="Black" BorderThickness="1"> <TextBlock> <TextBlock.Text> <MultiBinding Converter="{StaticResource SampleConverter}"> <Binding RelativeSource="{RelativeSource TemplatedParent}" Path="Text"/> <Binding RelativeSource="{RelativeSource TemplatedParent}" Path="Background"/> </MultiBinding> </TextBlock.Text> </TextBlock> </Border> </ControlTemplate>
This all works fine at runtime but at design time, I fail to load with the error:
Error 1 Unable to cast object of type 'MS.Internal.NamedObject' to type 'System.String'. C:\Users\jnak.REDMOND\Desktop\MultiBindingExample\MultiBindingExample\App.xaml 13 21 MultiBindingExample
If you attach another instance of Visual Studio and debug into this, you'll find this:
Ahh, I made an assumption that is always true at runtime but is not always true at design time.
There are situations like this where some of our hookup is still being worked out and your code needs to handle this in order to work correctly in the designer.
The simple fix here is to deal with this exception being thrown or check the type before trying to cast it (or use a construct such as "as" and check for null).
-- Project is attached for full repro.
As we approach the end of the Orcas project schedule (Visual Studio 2008) I've been doing some app building in order to help validate the quality of the product that we are shipping.
In doing so, I ran into a WPF bug that actually ended up costing me a fair amount of time.
I was trying to display a collection of teams where each team had a collection of tasks. I used an ItemsControl to display the teams and inside the ItemTemplate for each team, I had an ItemsControl that displayed the tasks.
I got the teams showing up but only the first task for the first team showed up. I went on trying to debug this, using TextBlock set to various binding expressions so that I could see the data as DataBinding would see the data. After a few hours of finding nothing wrong with my code, I hit the web and found that I had hit on a bug.
In fact, I found 2 bugs in this area:
https://forums.microsoft.com/MSDN/ShowPost.aspx?PostID=2202842&SiteID=1
http://forums.microsoft.com/MSDN/ShowPost.aspx?PostID=1275132&SiteID=1
There is an illustrative code sample in the forum posting so I didn't bother adding my own in this post. I did want to raise awareness to this issue as there is a workaround (reference the DataTemplate for the ItemTemplate from resources) and I hope I can save someone some time.
We made a change post Visual Studio 2008 (Orcas) Beta 2 in the way the AdornerProvider.Activate() and AdornerProvider.Deactivate() methods are called.
In the past you could assume that the Activate() and Deactivate() methods would be called exactly once per AdornerProvider instance.
This is no longer true, in Visual Studio 2008 RTM, it will be possible that for a given AdornerProvider instance, the Activate() and Deactivate() methods will be called multiple times. I had to update my TechEd samples to accomodate this.
Scott Guthrie, a GM in the division I work in (Developer Division) has just made a great post about how Microsoft will be releasing the source code of the .Net Framework libraries along with Visual Studio 2008.
I know, I know, the majority of you that develop using one of the .Net techologies use reflector, so this may not seem like a big deal but consider the demo that Scott walks through that shows the integration of debugging, source and symbols. This is such a win for .Net developers.
A really cool new feature for Visual Studio 2008 is the Toolbox Controls Installer which as the name implies, allows you to add controls to the toolbox. It's much easier than some of the previous VS package type work you used to need to do.
There is a great sample in the Visual Studio SDK 2008 that shows you how to add a Windows Forms control, Web Control and WPF control to the toolbox programmatically. The sample is called "Controls" -- and includes a WPF control and design time that may be familiar to those of you who have been reading my blog. (I'm surprised at how much mileage some of my samples get!)
There is one caveat, in some of the online documentation that doesn't show the WPF example (it was added later), there is a missing registry value that is required to make this work:
<Registry Root="HKLM" Key="$(var.VisualStudioRegistryRoot)\ToolboxControlsInstaller\CustomControlLibrary, Version=1.0.0.0, Culture=neutral, PublicKeyToken=1631b3e7a8fbcce5" Value="Litware Controls" Type="string"> <Registry Name="Codebase" Value="[#CustomControlLibrary.dll]" Type="string" /> <Registry Name="WPFControls" Value="1" Type="string" /> </Registry>
The "WPFControls" value above.
Very cool feature, really simplifies toolbox installation. Note: this is implemented in VS 2008 Beta 2.
There are a ton of WPF samples on MSDN which are really useful if you are developing in or learning WPF. If you aren't aware of these samples, please check them out.
One of the samples illustrates a point I want to make in my post today -- which is that as much as we try to avoid making you do this, sometimes you have to tweak your XAML/code in order to make it work within the Cider designer.
The sample I am going to use is from a DataTemplate Sample located here: http://msdn2.microsoft.com/en-us/library/aa972119.aspx
If you load up this sample in VS 2008 beta 2 or later and (after conversion) you'll see that you can build and run the application no problem. However, if you open up Window1.xaml, you'll notice that it fails to load in the designer and you'll get an error:
'myTaskTemplate' resource not found. at MS.Internal.Helper.FindResourceHelper.DoTryCatchWhen(Object arg) at System.Windows.Threading.ExceptionWrapper.InternalRealCall(Delegate callback, Object args, Boolean isSingleParameter) at System.Windows.Threading.ExceptionWrapper.TryCatchWhen(Object source, Delegate callback, Object args, Boolean isSingleParameter, Delegate catchHandler)
The problem occurs when we try to instantiate the TaskListDataTemplateSelector which is in the Window.Resources:
<local:TaskListDataTemplateSelector x:Key="myDataTemplateSelector"/>
This class does the following:
public class TaskListDataTemplateSelector : DataTemplateSelector { public override DataTemplate SelectTemplate(object item, DependencyObject container) { if (item != null && item is Task) { Task taskitem = item as Task; Window window = Application.Current.MainWindow;
if (taskitem.Priority == 1) return window.FindResource("importantTaskTemplate") as DataTemplate; else return window.FindResource("myTaskTemplate") as DataTemplate; }
return null; } }
Do you see the problem? What do you suppose Application.Current.MainWindow is in the designer?
Since Visual Studio isn't a WPF application, for a long time during the development of Cider Application.Current returned null. It no longer returns null but it is setup by Cider and has to do with Cider and not what you will get when you run your application.
In other words, calls to window.FindResource() will fail.
So what can you do to make this work in the designer? Well you have a few options, but if you've been following this blog for a long time you'll remember this post on detecting design mode.
Since this situation nicely demonstrates the use of design mode, I'll solve the problem that way by changing the above code to check for Design Mode before accessing Application.Current.
public override DataTemplate SelectTemplate(object item, DependencyObject container) { if (!DesignerProperties.GetIsInDesignMode(container) && item != null && item is Task) { Task taskitem = item as Task; Window window = Application.Current.MainWindow;
(. . .) }
The parameter you pass to GetIsInDesignMode doesn't really matter here since I'm counting on the default value which has been overriden to true in the context of a designer, the DependencyObject passed in will look for the IsInDesignMode attached property up it's tree.
Now -- lo and behold! The designer loads in Cider.
[One warning -- the code to set the binding via the Editing Object Model will not work in VS 2008 beta 2 but will work in RTM]
Recently I wrote a post about writing Design Time Adorners. I had a good question come up on our Cider forum and I thought I would answer that question in the form of a post and sample which will illustrate how to accomplish much of what the person who made the post was trying to do.
The question was essentially: How can I use design time adorners to make it easy to setup bindings between controls?
I've updated my sample from my post on writing Design Time Adorners to not only set the Background of my SimpleCustomControl but also to set the Background of all of the siblings of my control to bind to the Background of my control -- as long as my SimpleCustomControl has a name and the Background property of the sibling control is not set.
I piggy backed on the adorner that is already there so this post is really about using the Editing Object Model than adorners per se as the Editing Object Model is used in many situations, not just with adorners.
How the design time works
1) I start with my SimpleCustomControl in a Grid with 3 Buttons and 1 label as siblings. Notice that one of the Buttons has it's Background set.
2) I click on the Adorner for the SimpleCustomControl (the paint can that appears when the SimpleCustomControl is selected) and select a color.
3) Notice how the Background for the siblings that didn't have the Background property set is now set to a binding to the Background of my SimpleCustomControl.
So how did I get this to work?
Implementation
As mentioned above, I added all of the functionality to the existing handler for when the adorner gets clicked -- inside of _ColorsListControl_ClosePopup():
void _ColorsListControl_ClosePopup(object sender, RoutedEventArgs e){ (. . .)}
The first step is to get at the ModelItems for the siblings of the SimpleCustomControl. I get at them by going to the parent of the SimpleCustomControl (the Grid) and then asking for all of the Grids children.
ModelItem parentModelItem = adornedElementModelItem.Parent; ModelProperty siblingsProperty = parentModelItem.Properties["Children"]; ModelItemCollection siblings = siblingsProperty.Collection;
The next step is to enumerate through those ModelItems and if the Background property is not set, set it to a binding:
foreach (ModelItem mi in siblings) { if (mi != adornedElementModelItem && !mi.Properties[Control.BackgroundProperty].IsSet) { (. . .) // set to a Binding } }
Setting the Background to a Binding is done by creating a Binding instance, and setting the Background property of the sibling to it. Then we setup the Binding by setting both the ElementName and Path properties.
using (var scope = mi.BeginEdit()) { ModelItem binding = ModelFactory.CreateItem(Context, typeof(Binding)); mi.Properties[Control.BackgroundProperty].SetValue(binding); binding.Properties["ElementName"].SetValue(adornedElementModelItem.Properties["Name"].Value); binding.Properties["Path"].SetValue(new PropertyPath("Background"));
scope.Complete(); }
And that's it -- one caveat is that as all my posts, this is quick and dirty code to illustrate a concept and this code may have to be tweaked to handle all of the various situations that can arise in the usage of the SimpleCustomControl.
The code for this sample is attached to this blog post.
I've posted about design time adorners a few times in the past however there have been some API changes since then so I thought I'd do a fresh post.
What are design time adorners?
Adorners are WPF elements that you can put on the Cider design surface when your custom control is instantiated and selected. The end user who is using Cider can interact with your adorners to make updates to the XAML.
For example, the selection handles, grid lines and rails when a Grid is selected are put on the design surface through the same adorner infrastructure I am describing in this post.
Create an AdornerProvider
The first step is to create an AdornerProvider that will add your adorners when your control is selected. You will associate this AdornerProvider to your control using the Metadata Store as described in this post and this post.
public class MyCustomAdornerProvider : PrimarySelectionAdornerProvider { Public MyCustomAdornerProvider () { Button buttonAdorner = new Button(); buttonAdorner.Background = Brushes.Red; AdornerPanel panel = new AdornerPanel(); panel.Children.Add(buttonAdorner); Adorners.Add(panel);} (...) }
The key things to point out here are:
Add UIElements (the Adorners) to an AdornerPanel
Setup the AdornerPlacementCollection with Position and Size Methods that are additive (take a factor + offset):
In this API, "Content" refers to the control being adorned as its layed out on the design surface at 1X zoom.
For example, to setup a button that will be above and to the left of the associated control when it is selected, you would do the following:
AdornerPlacementCollection placement = new AdornerPlacementCollection();placement.SizeRelativeToAdornerDesiredHeight(1.0, 0);placement.SizeRelativeToAdornerDesiredWidth(1.0, 0);placement.PositionRelativeToAdornerHeight(-1, -NudgeAmount);placement.PositionRelativeToAdornerWidth(-1, -NudgeAmount);AdornerPanel.SetPlacements(adorner, placement);
To control how your adorners will act when zooming in and out, you can set the stretch properties on the AdornerPanel:
AdornerPanel.Set[Vertical/Horizontal]Stretch
Handle the interaction with your Adorners (handle events or setup tasks)
For a lot of adorners that you will put on the design surface, handling the interaction is as simple as handling the events on the adorner itself. For example:
BouncyButton adorner = new BouncyButton();adorner.Click += new RoutedEventHandler(OnButtonClick);
And in OnButtonClick() you can make changes to the XAML by working through the Editing Object Model. The following sample code shows how to get the ModelItem for a given adorner and modify one of its properties through the ModelProperty class.
ModelItem adornedElementModelItem = AdornerProperties.GetModel(_ColorsListControl);adornedElementModelItem.Properties[Control.BackgroundProperty].SetValue(new SolidColorBrush(_ColorsListControl.SelectedColor));
Hook up your AdornerProvider to the control via the FeatureAttribute and MetadataStore
The other thing you can do is use Tasks. A Task associates a collection of input gestures that are bound to commands and the handlers for those commands. Cider defines a set of ToolGestures that you can use as input gestures and thus handle -- in other words you can handle a click gesture on a Rectangle even though the Rectangle doesn't have a Click event.
Task clickTask = new Task();clickTask.ToolCommandBindings.Add(new ToolCommandBinding(_ClickCommand, OnRectangleClick));clickTask.InputBindings.Add(new InputBinding(_ClickCommand, new ToolGesture(ToolAction.Click)));AdornerPanel.SetTask(adorner, clickTask);
And as before, in OnRectangleClick() you can use the Editing Model (ModelItem, ModelProperty) to update the XAML from your adorner.
Setup the Metadata
Finally, in your Metadata Assembly, you will set up the metadata to associate your AdornerProvider to the control you want it to show up for:
builder.AddCustomAttributes(typeof(ButtonWithDesignTime), new FeatureAttribute(typeof(PopupButtonAdornerProvider)));
I've attached a sample to this blog post that shows all of the concepts above and will work on beta 2 and RTM.
One of our User Education writers who takes care of much of the Cider Extensibility documentation that will go into MSDN helped make the very early and beta set of docs on these subjects available for all to read.
You'll find them attached to this blog post.
You'll also find that aside from this blog and Brian and Soak's blog, there is very little little information out there on how to write things such as property editors, design time adorners, MenuActions (adding context menu items to the right click menu) etc. etc.. which makes these docs really helpful at this point in time.
Enjoy.
[Attached a sample project that shows the setup for metadata assemblies as well as adds a couple of MenuActions]
Now that you know how to add design time metadata vis Metadata Assemblies and how those Metadata Assemblies are found we can now move on to adding some extensibility features to your control.
Given a custom control, in this case, I'm just going to use a simple derived button:
public class ButtonWithDesignTime : Button { public ButtonWithDesignTime() { } }
When this ButtonWithDesignTime is loaded up in Cider and I right click on it, I want to add some items into the popup Context Menu so that it looks like this:
Where the SetBackground group and the Blue and Cleared items are the ones I added.
This menu items are called MenuActions and the grouping is called a MenuGroup. These are classes in Cider's Microsoft.Windows.Design.Extensibility.dll under the Microsoft.Windows.Design.Interaction namespace.
The other class in the namespace and assembly that is going to help us is the PrimarySelectionContextMenuProvider class which is the class that you derive from to add MenuActions when your control is the primary selection and is right-clicked.
The implementation follows, you simply instantiate MenuActions, put them in a MenuGroup and add that MenuGroup to the Items property that is provided by the PrimarySelectionContextMenuProvider class you are deriving from. Note that you can also add MenuActions directly to the Items collection.
class CustomContextMenuProvider : PrimarySelectionContextMenuProvider { private MenuAction _SetBackgroundToBlueMenuAction = new MenuAction("Blue"); private MenuAction _ClearBackgroundMenuAction = new MenuAction("Cleared");
public CustomContextMenuProvider() { _SetBackgroundToBlueMenuAction.ImageUri = new Uri("pack://application:,,,/CustomControlLibrary.VisualStudio.Design;component/Images/live_logo.png", UriKind.Absolute); _SetBackgroundToBlueMenuAction.Execute += new EventHandler<MenuActionEventArgs>(SetBackgroundToBlue_Execute);
_ClearBackgroundMenuAction.Execute += new EventHandler<MenuActionEventArgs>(ClearBackground_Execute);
// Flyouts with actions MenuGroup backgroundFlyoutGroup = new MenuGroup("SetBackgroundsGroup", "Set Background");
// if this is false, this group will not show up as a flyout but inline backgroundFlyoutGroup.HasDropDown = true; backgroundFlyoutGroup.Items.Add(_SetBackgroundToBlueMenuAction); backgroundFlyoutGroup.Items.Add(_ClearBackgroundMenuAction); this.Items.Add(backgroundFlyoutGroup);
// Called right before this provider shows its tabs, opportunity to set states UpdateItemStatus += new EventHandler<MenuActionEventArgs>(CustomContextMenuProvider_UpdateItemStatus); }
void CustomContextMenuProvider_UpdateItemStatus(object sender, MenuActionEventArgs e) { // your opportunity to update state, make changes before your context menu items are shown }
void ClearBackground_Execute(object sender, MenuActionEventArgs e) { ModelItem selectedControl = e.Selection.PrimarySelection; selectedControl.Properties[Control.BackgroundProperty].ClearValue(); }
void SetBackgroundToBlue_Execute(object sender, MenuActionEventArgs e) { ModelItem selectedControl = e.Selection.PrimarySelection; selectedControl.Properties[Control.BackgroundProperty].SetValue(Brushes.Blue); } }
Now all you have to do is tie up this CustomContextMenuProvider to the ButtonWithDesignTime. Since you already know how to add metadata, you simply have to do the following in your implementation of IRegisterMetadata:
builder.AddCustomAttributes(typeof(ButtonWithDesignTime), new FeatureAttribute(typeof(CustomContextMenuProvider)));
Which is essentially:
[Feature(typeof(CustomContextMenuProvider))]public class ButtonWithDesignTime {. . .}
Of course, as described in my metadata postings, you can't do that because that would require that you make references to the Cider assemblies from your runtime assembly.
[Update: please see: http://blogs.msdn.com/jnak/archive/2007/10/24/bug-in-finding-metadata-assemblies.aspx about a bug we have in this area]
Adding on to my last post about adding metadata to Cider and Blend's MetadataStore by using Metadata Assemblies I thought it would be good to talk about how those Metadata Assemblies are found.
If you have a control library assembly that is named MyControls.dll and it is not loaded from the GAC, the first place Cider will look for MyControls.Design.dll and then consequently MyControls.VisualStudio.Design.dll is in the same location that MyControls.dll is loaded. This is a little tricky because many of us, myself included sometimes forgot to change the build output directory of the Metadata Assembly (i.e. MyControls.Design.dll) to be the location where MyControls.dll is loaded.
The next location we will look, which typically will be the same location above is the location where MyControls.dll is referenced. For example, if you have a test application that uses the controls in MyControls.dll, it will need to add a reference to MyControls.dll and the corresponding Metadata Assembly will be loaded from that location as well. One situation where the location of MyControls.dll will be different from MyControls.Design.dll is when is loaded from the GAC. References aren't ever made to files in the GAC, so we look at the location of the reference to find the location to search for Metadata Assemblies.
Finallly, when MyControls.dll is loaded from the GAC, another location we will look is in the SDK folders which are defined by the entries in:
You can add additional directories to search for by adding a key whose default value is the path you want to add to the above registry key.
This is typically where 3rd Party Control Vendors will add an entry to point to the install location of their controls. This also puts the assemblies in the .Net tab of the Add References dialog and adds controls in the assemblies found in the AssemblyFolders to the list of items in the Choose Toolbox Items dialog.
Because we didn't want to have the .Net Tab of the Add References dialog or the Choose ToolBox Items Dialog to become overly cluttered with Metadata Assemblies, we also search a "Design" subfolder. This is the recommended location for your Metadata Assemblies when using the SDK folders.