简体   繁体   中英

XML data binding in WPF

I am attempting to write an app that decodes some unsigned long values. Format of each value is represented in XML as:

<Project Name="Project1">
 <Message Name="a">
  <BitField high="31" low="28">
   <value>0001</value>
   <value>1010</value>
  </Bitfield>
  <BitField high="27" low="17">
   <value>000111101</value>
  </BitField>
  <BitField high="16" low="0">100h</BitField>
 </Message>
</Project>

Now the project values appear in a combobox. When the user selects a value in combo box, the message types have to be displayed in list box. Then when the user selects a message type in the listbox, the bitfields and the values that they can hold have to be displayed. Now when the user selects a value for each bitfield, the final dword value have to be displayed in a textbox.

I have come across examples to parse a full xml but not pertaining to a selection. Need your guys help here.

One more thing is that user can enter a dword value in the text box. Now how can i do this reverse binding of decoding the dword in the textbox and display the corresponding message+value using the UI explained above?

UPDATE: Now i have done the binding between the combo box which displays the project values and the listbox which displays the message. The next thing i have to do is, when the user selects a message in listbox, the bitfields have to be displayed as rows with "high", "low", "value/@name"(not shown here), and then the value(binded to value/@name) as columns. The value/@name has to be displayed as combobox. Iam sure i can do this in dataGrid, but iam using .net 3.5 so searching for an alternative here. Also the value text block has to be editable incase the < value > nodes are not present in xml. Finally i got to pack the entries across "value" column to a DWORD. Can i do this in XAML without a datagrid? Whats an alternative to datagrid for .Net 3.5?

You have two different problems here -- an XML parsing problem and a conversion problem.

For the binding to work, you need to parse the XML into a class that represents it's values. For the conversion issue, I point you to concept of TypeConverters , which are fairly straightforward to use. For the XML parsing issue, I want to highlight LINQ-to-XML, which I think is the bees knees. A few lines of it can reconstitute even the most intricate of object graphs from an XML source. For your example of Project/Message/BitField, I've partially implemented a solution that will give you the pattern.

Note the "LoadFromXml" method. This uses a linq-to-xml expression that populates your object graph. The one thing that this query changes in your XML source is that it expects the collection nodes (ie or ) to be surrounded by collection nodes (ie )

   public class Project
    {
        public string Name { get; set; }
        public List<Message> MessageCollection = new List<Message>();

        public void LoadFromXml(string xmlString)
        {
            // From System.Xml.Linq
            XDocument xDocument = XDocument.Parse(xmlString);

            // The following assumes your XML is well formed, is not missing attributes and has no type conversion problems. In
            // other words, there is (almost) zero error checking.
            if (xDocument.Document != null)
            {
                XElement projectElement = xDocument.Element("Project");

                if (projectElement != null)
                {
                    Name = projectElement.Attribute("Name") == null ? "Untitled Project" : projectElement.Attribute("Name").Value;

                    MessageCollection = new List<Message>
                        (from message in xDocument.Element("Project").Elements("Messages")
                         select new Message()
                                {
                                    Name = message.Attribute("Name").Value,
                                    BitfieldCollection = new List<BitField>
                                        (from bitField in message.Elements("Bitfields")
                                            select new BitField() {High = int.Parse(bitField.Attribute("High").Value), Low = int.Parse(bitField.Attribute("Low").Value)})
                                }
                        );
                }
            }
        }
    }

    public class Message
    {
        public string Name { get; set; }
        public List<BitField> BitfieldCollection = new List<BitField>();
    }

    public class BitField
    {
        public int High { get; set; }
        public int Low { get; set; }
        public List<string> ValueCollection = new List<string>();
    }

You definitely need to keep in mind the separation of the model and view and external representation of the model. Depending on how big your actual XML is and that kind of API do tou feel confortable with you can use several quite different ways to read your XML and turn it into actual objects which will then serve as a model for your UI. It's just important to keep that separation in mind.

I will assume that your XML is actual data from some other processing - meaning that there will be a lot ot Project tags, having a lot of Message tags and that bitfield spans are different from message to message. If that assumption is wrong and bitfield spans are fixed either for the whole app or for all messages in one big file then you can save some time and space.

So assuming that everything is changable, the qickest way to grab values from tags migh be to just create XPathDocumen and select by XPath whihc you can keep as a string contant or even in a config. For example this would give you all Value tags (now called nodes)

//Project/Message/BitField/Value

and since objects for nodes have Parent pointers you can go collecting all values that share a Parent (into a list or array) then make an object for a parent (BitField), keep it and make sure it has the Parent pointer from XmlNode. Once you have all BitField objects you can so similar scan to group then into their parents (Message) and again to get Project-s. That's just one way, you can use shorter XPath to say start collecting from BitField and grab their children on your own.

You can also make a few plain classes to represent your XML tags verbatim (one data member for each field, one class for each tag) and put attributes for XmlSerialization on then. That will take a few trials to get names just right, but one you have it you be able to just "deserialize" the whole file with a function call and receive array of arrays or arrays :-) from .NET.

If your files are very, very big then you'll probably have to use XmlTextReader and scan the file from top to bottom - exact reverse of the XPath selection (XmlTextReader is the lightest way to parse, doesn't create tons of extra object but you have to iterate it in a few looks and always "walk" from parent to child since it's one-way).

UI-wise, if you have the time to experiment a bit, might be worth trying to construct a bounding box having 3 combo boxed and one text field and then a list of these - so that you have more flexibility ie you don't have to conform to mechanics of a grid.

Oh and if you have any influence on the XML format try to get ppl to always put that Value intoa value tag even if it's just one. XML file will be a bit longer, but your processing will be simplified.

This could be done using databinding and XMLReaders. First create your UI, preferably inside a parent panel (this way we can set the datacontext of the panel and the child controls will all inherit this context).

Next create a custom class (let's call it Project). Make sure each field in this class updates PropertyDependencyInfo, otherwise databinding will not work.

Then bind the Content or Itemssource properties of your UI controls to the fields of your Project class.

Then, create the code to read the XML using either an XMLReader class or an XPathReader class. Then, when you have accessed the data from the file, set up the fields of a new Project object to match the data you want to read.

Once the Project object has been instantiated and the values from the XML correctly assigned to it, set the parent Panel from the UI's DataContext property to your Project object. All of the UI fields should then update.

If you then need to load a different file, all you have to do is repeat the XML reading with a different file name.

As for your update: The rows and columns could be managed using either of the following:

A Grid/UniformGrid control whose RowDefinition and ColumnDefinition children are created and modified at runtime in order to manage increasing and decreasing numbers of fields

or: A set of StackPanels, one for each column (as the number of columns never changes). You can directly add Comboboxes to the StackPanel for your value/@name column or you could modify the StackPanel's ItemsTemplate (if it has one, I can't reach a machine to check at the moment).

This question should probably be split into a couple of sub-questions as it covers a range of topics and you're more likely to get detailed answers to each facet of your problem if there are questions tailored specifically to those problems.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM