Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Creating a WPF editor for XML file based on schema

Tags:

c#

xml

wpf

xaml

xsd

Here's the scenario. We use a large XML configuration file for one of our server products. This file is fairly well layed out and is validated against an XSD file.

It's time now though to build a configuration GUI for maintaining this file and I would like to dive into WPF to do it. I could lay out a seperate form for each config section, refactoring and redistributing every time we add an option to the config file but I'm hoping there is a more clever way of doing this.

Since I already have a strongly typed xml/xsd combination I am hoping there is an elegant method for building a UI to edit this easily enough. I know I could write an xml->xaml transformation but was hoping there is something out there to do the heavy lifting for me already?

Thanks in advance..

like image 880
Fergal Moran Avatar asked Dec 17 '09 15:12

Fergal Moran


3 Answers

How I'd do this:

I'd start by building a simple view-model class that wraps around an XmlElement and exposes it as a configuration option. This class could be extremely simple, e.g.:

public class OptionView
{
   private XmlElement XmlElement;
   public OptionView(XmlElement xmlElement)
   {
      XmlElement = xmlElement;
   }
   public string Name { get { return XmlElement.Name; } }
   public string Value 
   { 
      get { return XmlElement.InnerText; } 
      set { XmlElement.InnerText = value; }
   }
}

Now I can populate a collection of ElementView objects from an XmlDocument, add that collection to the window's ResourceDictionary, and format the objects with a simple DataTemplate, e.g.:

<DataTemplate x:Key="OptionViewTemplate" DataType={x:Type local:OptionView}>
   <Grid>
       <Grid.ColumnDefinitions>
          <ColumnDefinition SharedSizeGroup="Name"/>
          <ColumnDefinition SharedSizeGroup="Value"/>
       </Grid.ColumnDefinitions>
       <Label Content="{Binding Name}" Grid.Column="0"/>
       <TextBox Text="{Binding Value}" Grid.Column="1"/>
   </Grid>
</DataTemplate>
...
<ItemsControl Grid.IsSharedSizeScope="True"
    ItemsSource="{DynamicResource OptionCollection}"/>

(Note: Later, you can get fancy, and define subclasses of OptionView based on, for instance, the data type of the underlying XmlElement. Then you can define DataTemplates for each subclass, and as long as each presents the item in a two-column grid using that SharedSizeGroup, the second column can contain a date picker, or radio buttons, or whatever is appropriate to the subclass, and it'll all get neatly laid out at runtime.)

Once I got that working, which wouldn't take long, I'd start extending the OptionView class. For instance, if your schema is storing a human-readable label for an element in an xs:annotation element (and if it isn't, why not?), I'd make the Name property extract that out of the XmlElement's SchemaInfo property, instead of exposing the underlying element name.

Obviously I'd want to add validation, so I'd add a validation method that examined the XmlElement's SchemaInfo property and interpreted it. (Assuming that the elements you're validating are simple content, that shouldn't be hard.) There's a million tutorials on how to implement validation in WPF applications, so I won't go into too much detail here.

If there are tons of configuration options and you have some intelligent way of grouping them into categories, I'd build a higher level class that exposed (at least) two properties - a string CategoryName property and an OptionsViews collection - populate it from the XML document, and add it to the window's ResourceDictionary. Within the window, I'd bind it to a TabControl, e.g.:

<TabControl ItemsSource="{DynamicResource OptionCategories}">
   <TabControl.ItemContainerStyle>
      <Style TargetType="{x:Type CategoryView}">
         <Setter Property="Header" Value="{Binding Path=CategoryName}"/>
         <Setter Property="Content" Value="{Binding Path=OptionsViews}"/>
         <Setter Property="ContentTemplate" Value="{StaticResource OptionViewTemplate}"/>
      </Style>
   </TabControl.ItemContainerStyle>
</TabControl>

Or to some item control whose item container template creates an Expander. Or something. (All code guaranteed untested! Most of it was copied out of working projects, though.)

If you haven't done anything with WPF before, this is a pretty good project to start on. It'll expose you to the fundamentals of data binding and items controls and validation, and the end result will be something that's useful and probably looks pretty good.

And you'll notice that while the markup involved in creating the templates is pretty verbose, there are only two templates. The only code in the application (so far) is the code that exposes the XmlElements to the UI.

like image 102
Robert Rossney Avatar answered Sep 21 '22 14:09

Robert Rossney


To present simple xml configs (if custom editors for values are not required) one can directly bind XElement to a view using HierarchicalDataTemplate.

xaml:

<TreeView Grid.IsSharedSizeScope="True" 
          ItemsSource="{Binding Xml.Elements}">
    <TreeView.ItemTemplate>
        <HierarchicalDataTemplate ItemsSource="{Binding Elements}">
            <Grid>
                <Grid.ColumnDefinitions>
                    <ColumnDefinition SharedSizeGroup="Name"/>
                    <ColumnDefinition SharedSizeGroup="Value"/>
                </Grid.ColumnDefinitions>
                <Label Content="{Binding Name}" />
                <!--Show TextBox only for leaf elements-->
                <TextBox Grid.Column="1"
                         Text="{Binding Value}" 
                         Visibility="{Binding HasElements,
                            Converter={StaticResource reverseBoolToVisibilityConverter}}"/>
            </Grid>
        </HierarchicalDataTemplate>
    </TreeView.ItemTemplate>
</TreeView>

view model:

class ConfigViewModel:INotifyPropertyChanged
{
    public XElement Xml { get; private set;}

    //example of persistence infrastructure
    public event PropertyChangedEventHandler PropertyChanged = delegate { };
    public void Load(string fileName)
    {
        Xml = XElement.Load(fileName);
        PropertyChanged(this, new PropertyChangedEventArgs("Xml"));
    }
    public void Save(string fileName)
    {
        Xml.Save(fileName);
    }
}

There are some good examples for reversed bool to visibility converter.

like image 43
igorushi Avatar answered Sep 23 '22 14:09

igorushi


Not WPF but very enlightening - A Dynamically Generated XML Data Editor by Marc Clifton

an article with source code for windows forms about creating a GUI for editing an XML based on XSD.

Have been searching long for something like that.

like image 44
CaptainNemo Avatar answered Sep 23 '22 14:09

CaptainNemo