Customize property editor in the VS designer
Sometimes, the VS property grid in the XAML designer (the code name is Cider) is not enough for custom WPF controls. Once, I needed to update a property of my control automatically depending on a value of another property. First, I searched for an example of such an action in WPF controls of the framework. I found nothing similar. The property grid in VS is plain and one property never depends on another. However the property grid in Expression Blend is much more interesting. Therefore, there should be a way to extend the designer. The way starts from the CategoryAttribute and EditorAttribute atributes. Let’s see an example of a custom WPF control that extends Label. The extended control renders Content and adds prefix before the content and suffix after the content. So our control has two additional properties: Prefix and Suffix. The control is for names. Therefore, Prefix can only be “Mr” or “King”. When the user selects Prefix, Suffix gets an initial value depending on the selected Prefix. Then the user can edit Suffix if she is not happy with the value set automatically. Say the prefix “Mr” correspons to the suffix “Junior” and the prefix “King” corresponds to the suffix “III”. So if the user inputs “Arthur” to Content and selects the prefix “King”, the suffix will be set to “III” automatically. See the picture of the control at runtime below:
It looks identically in the designer. The following picture is the part of the property grid containing Content, Prefix and Suffix:
Note, Suffix can be edited after selecting Prefix because the king is not necessary III. He can be I, II or even IV. But if you set the prefix to Mr, the edited value of suffix will be overriden with Junior.
Let’s see the class declaration of the custom control (Control1).
[Editor(typeof(ExtCategoryEditor), typeof(ExtCategoryEditor))] public class Control1 : Label {
The Editor attribute says that we provide a editor that can be embedded into the VS property grid for the control. In fact the part of the property grid under “Ext” is a grid that ExtCategoryEditor provides. Our editor is only for the Ext category. In Control1 class we also need to say which properties correspond to “Ext ” category, so that they may get into our editor. We simply mark properties with CategoryAttribute for this:
[Category("Ext")] public string Prefix { get { return (string)GetValue(PrefixProperty); } set { SetValue(PrefixProperty, value); } }
You probably think that ExtCategoryEditor is the class that contains the Grid control we see in the property grid. That’s not right. ExtCategoryEditor is a class derived from CategoryEditor that has a set of methods. One of them gives DataTemplate containing the Grid control. Look at the ExtCategoryEditor class:
internal class ExtCategoryEditor : CategoryEditor { public override bool ConsumesProperty(PropertyEntry property) { string[] properties = new string[] { "Prefix", "Suffix", "Self" }; if (properties.Contains(property.PropertyName)) { return true; } else { return false; } } public override DataTemplate EditorTemplate { get { return (DataTemplate)EditorTempaleForExt.Current["ExtDataTemplate"]; } } public override object GetImage(System.Windows.Size desiredSize) { return null; } public override string TargetCategory { get { return "Ext"; } } }
EditorTemplate is the method giving Grid, which is in EditorTemplate. The Editor template is declared in ResourceDictionary EditorTemplateForExt. Its XAML declaration is below:
<DataTemplate x:Key="ExtDataTemplate"> <Grid> <Grid.ColumnDefinitions> <ColumnDefinition/> <ColumnDefinition/> </Grid.ColumnDefinitions> <Grid.RowDefinitions> <RowDefinition/> <RowDefinition/> </Grid.RowDefinitions> <TextBlock Grid.Row="0" Grid.Column="0" Text="Prefix"/> <ComboBox Grid.Row="0" Grid.Column="1" DisplayMemberPath="Text" SelectedValuePath="Text" SelectedValue="{Binding Prefix, ElementName=uiPropertyCourier}" SelectionChanged="ComboBox_SelectionChanged"> <ComboBox.Items> <TextBlock Text="King"/> <TextBlock Text="Mr"/> </ComboBox.Items> </ComboBox> <TextBlock Grid.Row="1" Grid.Column="0" Text="Suffix"/> <TextBox Name="uiSuffix" Grid.Row="1" Grid.Column="1" Text="{Binding Suffix, ElementName=uiPropertyCourier}" /> <local:PropertyCourier x:Name="uiPropertyCourier" Visibility="Collapsed" Prefix="{Binding [Prefix].PropertyValue.Value}" Suffix="{Binding [Suffix].PropertyValue.Value}"> </local:PropertyCourier> </Grid> </DataTemplate>
You can see that “Binding [<Property name>].PropertyValue.Value” in Microsoft’s examples. It means that DataContext of your DataTemplate and the elements inside it is an instance of some internal Microsoft class. The class has an Indexer that receives string as a parameter. It actually gets the property name. I won’t write the names of those Microsoft classes because they declared with internal keyword and are not supposed to be used by developers outside Microsoft. However you can see them in the debugger. Just add watch to the DataContext property.
This is ComboBox_SelectionChanged method:
private void ComboBox_SelectionChanged(object sender, SelectionChangedEventArgs e) { ComboBox comboBox = (ComboBox)sender; PropertyCourier propertyCourier = (PropertyCourier)comboBox.FindName("uiPropertyCourier"); string value = (string)comboBox.SelectedValue; switch (value) { case "King": propertyCourier.Suffix = "III"; break; case "Mr": propertyCourier.Suffix = "Junior"; break; } }
PropertyCourier is an empty FrameworkElement with two dependency properties: Prefix and Suffix. It is just an intermediate object that pass over values from our controls to the instances of the internal microsoft classes. The point is that you can not update properties in Control1 directly because of two reasons. Firstly, It is not convenient to get access to the Control1 object from the method. Secondly, the values won’t be serialized. Values should pass through internal Cider classes to be serialized. I use PropertyCourier as a convenient way to update values through Cider classes.
For more details download the code.
How to load referenced assemblies from any place you want
This is HowTo for those who need it. The app.config file below defines rules for the main assembly. It sets where the referenced assemblies could be found at runtime. The main assembly references other two assemblies and gets one of them from network (\\someserver\shared\Summator.dll) and another one from the local machine in a subdirectory.
Application config file is below:
<configuration>
<runtime>
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
<dependentAssembly>
<assemblyIdentity
name="Summator"
publicKeyToken="03840bb05afb3f17"
culture="neutral" />
<codeBase
version="1.0.0.0"
href="file:///\\someserver\shared\Summator.dll"/>
</dependentAssembly>
<probing privatePath="SUB1\SUB2\SUB3\">
</probing>
</assemblyBinding>
</runtime>
</configuration>
If you reference an assembly from your local machine, you are not limited by subdirectories of the main application because you can use the following method to mount your drives to one of the subdirectories:
http://support.microsoft.com/kb/307889
PS: Configuration files can be conveniently edited by the mscorcfg.msc tool (Control Panel – Administrative Tools – Microsoft .NET Framework 2.0 Configuration, MyComputer, Application, Right click, Add…).
The puzzle about a car and two goats
A collegue of mine gave me the following puzzle to solve:
Suppose you’re on a game show, and you’re given the choice of three doors. Behind one door is a car, behind the others, goats. You pick a door, say number 1, and the host, who knows what’s behind the doors, opens another door, say number 3, which has a goat. He says to you, “Do you want to pick door number 2?” Is it to your advantage to switch your choice of doors?
In the evening I wrote a simple console application that proves the solution practically running the game 10000 times. You can compile it in command line “csc.exe GoatSelector.cs” and execute GoatSelector.exe.
The application shows you validation of the .NET random engine first. There are percentages of 0, 1, 2 numbers got from the Random class executed for 10000 times. Then it shows percentage of success when you “pick door number 2” (switching your choice) and percentage of success when you stay with your first selected door. Download the code
Effect of XML:Space=”preserve”
I was working with XAML created by Expression Blend. At runtime I noticed that a TextBlock moved on the right and one line down from the position it was in the designer. It took me some time to find the cause of the discrepancy. Below there is XAML of two TextBlock elements:
<TextBlock Height="17" VerticalAlignment="Top" xml:space="preserve"><Run xml:space="preserve">first line</Run></TextBlock> <TextBlock Margin="0,22,0,0" Height="23" VerticalAlignment="Top"> Second line </TextBlock>
In the designer it looks like this:
When the application is running, the picture is different:
As you see, the second line moved on the right and one line down.
The only unusual part of XAML is the xml:space=”preserve” attribute, which is set at the first TextBlock and the Run elment inside it. This attribute says that all the indentation and spaces in XAML code should be saved at the time of rendering. But according to MSDN the attribute scope is only the content of the element where the attribute is defined. It’s defined at the first TextBlock and the contained Run element. If you remove the attribute from those elements everything renders correctly. But why does the attribute from the first TextBlock influences the second TextBlock. In the debugger I stopped the application and saved XAML of the “second line” TextBlock and it really had xml:space=”preserve” attribute. I assume it somehow came from the “first line” TextBlock.
It is apparently a bug in WPF. The bug only apears If you have more than one nested elements with the xml:space =”preserve” attribute defined at both. The next sibling element after those will have this attribute set to “preserve” implicitly.
The solution is simple: you need to set xml:space=”default” explicitly at the next element. So our XAML will be:
<TextBlock Height="17" VerticalAlignment="Top" xml:space="preserve"><Run xml:space="preserve">first line</Run></TextBlock> <TextBlock Margin="0,22,0,0" Height="23" VerticalAlignment="Top" xml:space="default"> Second line </TextBlock>
regular expressions for XML tags
Regular expressions are powerful and can substitute XML libraries for simple tasks. Say, you need to select all elements with a specific name from an XML file. Below is a sample doing that. The program reads XMLFile11, selects three different elements and prints them and their content to the console.
XMLFile1:
<?xml version="1.0" encoding="utf-8" ?> <Main> <Item Name="item1"/> <Item Name ="item2"> <Components> <Component/> <Component/> <Component Name="component10"> <SubComponent/> </Component> </Components> </Item> <Item> <Item></Item> <Item></Item> </Item> </Main>
C# code:
class Program { public Program(string Xml) { _xml = Xml; } string _xml; void PrintTags(string tagName) { string expression = @"(<{0}\/>)|" + // gets <tagName/> @"(<{0}\s[^>]*?\/>)|" + //<tagName[space]BlaBla.../> @"(<{0}>[\s\S]*?<\/{0}\s*>)|" + //<tagName>BlaBla...</tagName> @"(<{0}\s[\s\S]*?>[\s\S]*?<\/{0}\s*>)"; //<tagName[space]BlaBla...>BlaBla...</tagName> Regex regex = new Regex(String.Format(expression, tagName)); Match match = regex.Match(_xml); do { Console.WriteLine("tag: {0}", tagName); Console.WriteLine(match.Value); match = match.NextMatch(); } while (match.Success); } void Run() { PrintTags("Item"); PrintTags("Component"); PrintTags("Components"); } static void Main(string[] args) { Program program = new Program(File.ReadAllText("XMLFile1.xml")); program.Run(); Console.Read(); } }
I had to define expresions for four cases (see comments in the PrintTags method).
The following is the output:
Output:
tag: Item <Item Name="item1"/> tag: Item <Item Name ="item2"> <Components> <Component/> <Component/> <Component Name="component10"> <SubComponent/> </Component> </Components> </Item> tag: Component <Component/> tag: Component <Component/> tag: Component <Component Name="component10"> <SubComponent/> </Component> tag: Components <Components> <Component/> <Component/> <Component Name="component10"> <SubComponent/> </Component> </Components>
However this code won’t work if XML file contains nested elements with identical names. See example below.
XML:
<Item>
<Item></Item>
<Item></Item>
</Item>
If you call the PrintTags method with “Item”, you will get <Item><Item></Item>. It happens because the regular expression doesn’t count opened and closed tags.
-
Recent
- Remote desktop via VPN from SUSE 11.1
- EVPO DC project structure
- EVPODC Getting Started Part 2 (Configuration)
- EVPODC getting started part 1 (installation)
- Customize property editor in the VS designer
- How to load referenced assemblies from any place you want
- The puzzle about a car and two goats
- Effect of XML:Space=”preserve”
- regular expressions for XML tags
-
Links
-
Archives
- May 2009 (2)
- November 2008 (2)
- June 2008 (5)
-
Categories
-
RSS
Entries RSS
Comments RSS



