Binding ItemsSource to an XML String - wpf

I have a database stored procedure call that, among other columns, returns a column that contains data in XML format. I'm trying to display the results of this call in a grid (the call is made through Entity Framework, so the actual objects bound to the grid are POCO's, and the column in question is a string property).
While the normal columns display correctly, I need to be able to take the XML data in that one column and bind its contents (it will contain multiple nodes) to an ItemsControl within the template for the cell.
For example, let's say I have a grid that displays a collection of the following object:
class Photo
{
string PhotoId { get; set; }
string Name { get; set; }
string TagListXml { get; set; }
}
This is intended to represent a photo, and the TagListXml property contains an XML string listing all of the tags that have been applied to the photo. Something akin to...
<PhotoTags>
<Tag>Faces</Tag>
<Tag>People</Tag>
<Tag>Sepia</Tag>
</PhotoTags>
(While obviously a normal POCO would have a List<string> or something like that, let's just assume for the moment that I must use an XML string)
In my grid, I want to be able to specify an ItemsControl that uses this XML and, ultimately, gives me items Faces, People, and Sepia.
I've tried this for a cell template:
<DataTemplate>
<ItemsControl ItemsSource="{Binding TagListXml,
Converter={StaticResource xmlConverter}}" />
</DataTemplate>
Where xmlConverter is defined as such:
<dc:StringToXmlConverter x:Key="xmlConverter" XPath="PhotoTags" />
And dc:StringToXmlConverter is a custom IValueConverter that just takes a string value, instantiates an XmlDocument and loads the string, then returns an XmlDataProvider with that new document and the XPath specified above.
While this does not produce any errors either in the form of an exception or a binding error in the Output window, it doesn't do anything (there are no results displayed).
I believe this is because an XmlDataProvider cannot be set to the ItemsSource directly, but rather must be set as the Source of a Binding. (in other words, you must do ItemSource="{Binding Source={StaticResource xmlProvider}}" rather than ItemsSource="{StaticResource xmlProvider}").
I can't seem to get anywhere with it, and I've been banging my head on this for the last couple of hours.
How can I bind an XML string to the ItemsSource of an ItemsControl?

Why not return an XmlNode[] instead of a XmlDataProvider (which is mainly for XAML anyway)?

Related

WPF DataGrid with mutable row/cell types - reuse templates

I have a complex table of data (about 150 rows, between 1 and 100 columns) which I want to display and edit using a DataGrid in WPF, but I've hit a big stumbling block. Please forgive me (and correct me) if my terminology is off in points, as I'm quite new to WPF and XAML.
To understand my problem, my requirements are:
The data consists of a variable number of rows and columns which are loaded through AJAX
Every row (class "Record" in my test implementation) has a few fixed properties that need to be displayed as well as a varying number of properties (though all rows have the same number of such properties) in a collection
Each row/Record has an type (e.g. String, Integer, Boolean) for its properties inferred through an Enum property "VType". Properties should be displayed and edited with a template according to the VType value.
Columns may be added or removed at run time
(Some) rows may also be added or removed at run time
Rows can change their "type" at run time
So far, I've built a working example with DataGridTextColumns that creates the columns from simulated data and fills the bound collection. I've implemented INotifyPropertyChanged and used ObservableCollections where necessary, so reactivity works, and my propoerty values are pulled from the binding to the individual property and correctly shown.
When adding the columns, I passed the correct binding. For my example app, I use the column index to bind each column to the correct Property object in the Record's collection:
// Amounts to "Properties[0].Value", "Properties[1].Value", etc.
var binding = new Binding(string.Format("Properties[{0}].Value", column.Index));
dataGrid.Columns.Add(new DataGridTextColumn() { Header = column.Name, Binding = binding });
Now I tried to tackle using different templates for different "Record types", i.e. a vType property in my Record class. I've created data templates in Window.Resources (very crude ones to start), set up a lookup and implemented the RecordTemplateSelector:
<!--BOOL TEMPLATE-->
<DataTemplate x:Key="booleanTemplate">
<CheckBox IsChecked="{Binding Value}" Background="LightGray" Margin="5, 0, 0, 0"/>
</DataTemplate>
<!--STRING TEMPLATE-->
<DataTemplate x:Key="stringTemplate">
<TextBlock Text="{Binding Value}"/>
</DataTemplate>
<!--INTEGER TEMPLATE-->
<DataTemplate x:Key="integerTemplate">
<TextBlock Text="{Binding Value}"/>
</DataTemplate>
<local:RecordTemplateSelector x:Key="myRecordTemplateSelector"
BooleanTemplate="{StaticResource booleanTemplate}"
StringTemplate="{StaticResource stringTemplate}"
IntegerTemplate="{StaticResource integerTemplate}"/>
And this is my TemplateSelector:
class RecordTemplateSelector : DataTemplateSelector
{
public DataTemplate StringTemplate { get; set; }
public DataTemplate BooleanTemplate { get; set; }
public DataTemplate IntegerTemplate { get; set; }
public override DataTemplate SelectTemplate(object item, DependencyObject container)
{
var selectedTemplate = StringTemplate;
var record = item as Record;
if (item == null) return selectedTemplate;
switch (record.VType)
{
case Record.ValueType.Checkbox:
selectedTemplate = BooleanTemplate;
break;
case Record.ValueType.Integer:
selectedTemplate = IntegerTemplate;
break;
case Record.ValueType.String:
selectedTemplate = StringTemplate;
break;
}
return selectedTemplate;
}
}
It does pull the correct template, but when I thought I had it working, I noticed that I can't correctly bind my DataGridTemplateColumn - it is always implicitly bound to the whole row (i.e. Record object) and I don't see a way how my template can know which element in the Record's Property collection it should apply to.
I'm at a loss where to go from here. Is there a way to inherit the column's binding down to the template? Is there some other way to pass the correct item (an index would be okay too) to the template? Or do I have to use a completely different approach?
Big Thanks in advance for any input you can give me.

Setting SelectedItem of Listbox or ComboBox through ViewModel MVVM WPF

Objective: having bound the SelectedItem of a ListBox (or ComboBox) to an instance of an object through xaml, I would like to set the selected instance of the object through the view model and have it reflect on the ListBox or ComboBox.
<ComboBox x:Name="cboServers" HorizontalAlignment="Left" Margin="535,694,0,0" VerticalAlignment="Top" Width="225"
ItemsSource="{Binding Settings.Servers}"
SelectedItem="{Binding Settings.SelectedServer, Mode=TwoWay}"
DisplayMemberPath="UserFriendlyName">
C# Model View code
public ObservableCollection<AutoSyncServer> Servers { get; set; }
private AutoSyncServer _selectedServer;
public AutoSyncServer SelectedServer
{
get { return _selectedServer;}
set
{
_selectedServer = value;
OnPropertyChanged("SelectedServer");
}
}
The list or combo box populates correctly. Selecting an item on the ListBox or ComboBox will correctly set the SelectedServer object.
However, if I try to write a set statement in C# such as:
Servers.Add(newServer);
SelectedServer = newServer;
The ListBox or ComboBox will correctly add the item and the SelectedServer object will be correctly set on the MVVM model, but the front end will not reflect this selection.
In this specific case, an xml file is read saying what the user had selected last, and when the window opens the ComboBox has nothing selected (the servers are all loaded correctly within it though)
What's missing here?
The actual object in SelectedItem must be an object instance which is found in the Servers collection, in the Object.ReferenceEquals(a, b) sense. Not just the same Name and ID (or whatever) properties; the same exact class instance.
The classic case where people run afoul of this is deserializing equivalent items in multiple places. Servers has a collection of deserialized AutoSyncServer instances, and Settings.SelectedServer is a separately deserialized AutoSyncServer instance, which has identical property values to one of the items in Servers. But it's still a different object, and the ComboBox has no way of knowing that you intend otherwise.
You could override AutoSyncServer.Equals() to return true if the two instances of AutoSyncServer are logically equivalent. I don't like doing that because it changes the semantics of the = operator for that class, which has bitten me before. But it's an option.
Another option is to have one canonical static collection of AutoSyncServer and make sure every class gets its instances from that.
I don't understand why this code didn't work, given the above:
Servers.Add(newServer);
SelectedServer = newServer;
Once newServer is in Servers, it should be selectable. I tested that and it's working for me as you would expect.
i think you must avoid "sub-bindings", they work once when the view ask for, but not well after
Settings.SelectedServer ==> SelectedServer
and if you comment OnServerChanged?.Invoke(this, _selectedServer); what is happening ? it works ?

WPF Bind a DataGrid to all available variables in a class

So I have 4 classes, for example:
Class A Inherits I
Public name as String
Public ID as Integer
Class B Inherits I
Public test as String
Public somethingElse as Integer
Class C Inherits I
Public banana as String
Public Type as String
Public length as Integer
Class D Inherits I
Public name as String
Public ID as String
Say I have a ComboBox in my WPF application that contains a list of I objects (some are of type Class A, some of type Class C, etc etc etc.).
When one is selected, I want the datagrid to populate with the public variables of the selected class - variable names in the left column and values in the right.
I want the right-hand column to be editable, but not to update the variables in the class directly.
My questions are, then - how do I bind the datagrid to the selected class if all/some of the variables are different in each class? And how do I then keep the association with the variable, so I can update it later if the changes the user makes passes my custom validation?
My idea (that I don't know how to implement):
Would each class need some sort of converter method that the DataGrid can bind to? but if so, what would that method return?
Would I just have to keep track that row 1 contains this variable, etc. for each class, so I can update later?
Perhaps if you had tried this before asking your question, then you would already have your answer? In WPF, the DataGrid control provides functionality that will auto generate its columns. In fact, that is the default behaviour and you have to turn it off if you don't want it. In your case however, it sounds like you want it left on.
You'll need an ObservableCollection<I> property in your code behind or view model to data bind to the DataGrid.ItemsSource property... let's call it Items:
<DataGrid ItemsSource="{Binding Items}" />
Now here's the tricky part when you change the type of objects in the collection and have to attempt to data bind all of the new properties to the DataGrid.Columns collection:
List<A> classAItems = GetClassAItems();
Items = new ObservableCollection<I>(classAItems);
That's it! Show a different class?:
List<B> classBItems = GetClassBItems();
Items = new ObservableCollection<I>(classBItems);
That's it!
UPDATE >>>
how can I have two columns - "field" and "value", with each variable populating a row?
I seem to have inadvertently answered your follow up question earlier, rather than you actual question. To get all column headers correctly populated, you should use an ObservableCollection<object> instead. To have only the common properties populated in the column headers, you should declare an interface with just those properties in. After implementing that interface in all of your classes, you will then be able to display them all in the same DataGrid (even mixed in the same collection).

Using base control in XAML, but loading a derived control

Here's a situation I am trying to solve:
I have a base UserControl from which I derive a number of other Controls that handle derivations of a base Object in a specific manner. (The purpose of this being to create a template for when additional derivations of the base control are needed later down the road.) What I would like to do is use the base control name as the tag in XAML, but when the control is actually rendered, show the derived control.
class BaseControl : UserControl { }
class DerivedControl1 : BaseControl { }
class DerivedControl2 : BaseControl { }
class BaseObject { }
class DerivedObject1 : BaseObject { // Requires DerivedControl1 to display }
class DerivedObject2 : BaseObject { // Requires DerivedControl2 to display }
class BaseContainerObject { }
class ContainerObject1 : BaseContainerObject
{
DerivedObject1 dObject0;
DerivedObject1 dObject1;
DerivedObject2 dObject2;
}
class ContainerObject2 : BaseContainerObject
{
DerivedObject2 dObject0;
DerivedObject2 dObject1;
DerivedObject1 dObject2;
}
window.xaml
<!-- Here is what I would like to do -->
<StackPanel>
<BaseControl Name="Object0" DependencyProperties="{Binding BaseContainerObject.dObject0}" />
<BaseControl Name="Object1" DependencyProperties="{Binding BaseContainerObject.dObject1}" />
<BaseControl Name="Object2" DependencyProperties="{Binding BaseContainerObject.dObject2}" />
</StackPanel>
I've played around with styles and data triggers to detect the specific type of ContainerObject, but I haven't found the right pattern to encapsulate a ContainerObject in a single template-able "package" yet.
I could dynamically add the controls from the code-behind, but I haven't had any luck with that so far. (The top-level of the control appears on VisualTree, but no children appear on the tree and none are rendered.)
Any thoughts?
EDIT:
I can't post a screenshot at the moment, but perhaps I can add a little more detail.
I have a data object (the DataContext for the window) that has up to nine attributes (the DerivedObjects) that the user will need to edit in my window. The meaning of those nine attributes, and, in turn, how they should be expressed in UI controls, changes based on the attributes of a second data object the user selects in a previous step. (That is the ContainerObject. The other data object is not referenced in the above code, although it contains a reference to the second data object.)
Those attributes can be expressed in four different ways: a text box (for continuous values), a combobox (for discrete values), a checkbox (for boolean values) and radio buttons (for a choice between two values).
I have created UserControls that package those controls in a horizontal Grid with 1) a label for the value's definition, 2) the value's units (if applicable) and, if applicable, 3) a checkbox to view the value in an alternate format (i.e. viewing a decimal number in hex). (Those are the DerivedControls that inherit from an XAML-less BaseControl that stores common properties and functions.) To maintain proper column alignment over the entire collection, I specify four column widths in a Style at the Window level and use a Converter to handle alignment for attributes that do not require the units and/or the alt-display checkbox.
When the user selects the second object in the previous step, the nine rows of the collection control should look to the second data object reference of the DataContext object to select the proper template and populate the other labels. Because I will need to use this collection in other programs, I am creating it in a separate assembly.
I know I am pigeon-holing myself in some fashion on this. I am trying to do this with as little code as possible, but I can't think of the right code pattern to use here. Every component is working fine, but I can't seem to get it all to come together in a simple way so I can work out the last few little bugs.
Thanks. I am just learning WPF, and I really like. I'm just at the point of trying to get my head wrapped around some of the finer details.
Here is a pretty good example from wpftutorial.net of what it sounds like you need. To summarize, you can use a DataTemplate to define how an object is displayed within a repeating control such as a ListBox, ComboBox or ListView. You can override the styles of those to make them appear as you want, or sometimes it's just easier to use ItemsControl (the control they inherit from) directly. They have a property named ItemsPanel that will allow you to specify a StackPanel as the ItemsPanelTemplate so you get the same desired layout of the objects as you showed above.
Setting how an object is dispalyed via a DataTemplate is great, but you want to dynamically change that template based on the type of the bound object if I understand correctly. This can be accomplished by creating a DataTemplateSelector.
public class PropertyDataTemplateSelector : DataTemplateSelector
{
public DataTemplate DefaultDataTemplate { get; set; }
public DataTemplate DerivedObject1Template { get; set; }
public DataTemplate DerivedObject2Template { get; set; }
public override DataTemplate SelectTemplate(object item,
DependencyObject container)
{
DataTemplate selectedTemplate = DefaultDataTemplate;
if (item is DerivedObject1)
{
selectedTemplate = DerivedObject1Template
}
else if (item is DerivedObject2)
{
selectedTemplate = DerivedObject2Template;
}
return selectedTemplate;
}
}
And then your XAML can use the template selector on the repeating control:
<Window x:Class="Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:..."
xmlns:sys="clr-namespace:System;assembly=mscorlib">
<Window.Resources>
<!-- Default DataTemplate -->
<DataTemplate x:Key="DefaultDataTemplateResource">
...
</DataTemplate>
<!-- DataTemplate for Booleans -->
<DataTemplate x:Key="DerivedObject1TemplateResource">
<local:DerivedControl1 .../>
</DataTemplate>
<!-- DataTemplate for Enums -->
<DataTemplate x:Key="DerivedObject2TemplateResource">
<local:DerivedControl2 .../>
</DataTemplate>
<!-- DataTemplate Selector -->
<local:PropertyDataTemplateSelector x:Key="myCustomTemplateSelector"
DefaultnDataTemplate="{StaticResource DefaultDataTemplateResource}"
DerivedObject1Template = "{StaticResource DerivedObject1TemplateResource}"
DerivedObject2Template = "{StaticResource DerivedObject2TemplateResource}"/>
</Window.Resources>
<Grid>
<ItemsControl ItemsSource="{Binding}" ItemTemplateSelector="{StaticResource myCustomTemplateSelector}"/>
</Grid>
</Window>
Hopefully that will get you started!

Where can I use a DataTemplate in something that isn't a repeater type of control?

Ok, I know it's kind of weird, but I am trying to create a data driven panel that reconfigures itself based on a DataTemplate member that I have on a object. Right now I am accomplishing this by using an ItemsControl bound against a dummy list of 1 bogus item so that I get a single instance of the data template. It just seems silly to have to do this in an ItemsControl, but I can't think of anything that will use my DataTemplate without trying to do it against a list of items. Anyone have any idea?
Just for clarity, let's say I have a Widget class:
public class Widget
{
public string Name { get; set; }
public DataTemplate MyTemplate { get; set; }
public List<object> DummyList = new List<object> { new object(); }
}
and the Xaml something like:
<ItemsControl ItemsSource={Binding DummyList} ItemTemplate={Binding MyTemplate}/>
I can then create a collection of Widgets and populate each one with the correct data template based on the object's status.
Anyway, as I said, this works... I'd just like to find a more elegant solution than using an ItemsControl if anyone knows of one.
Chances are that you could also just set ContentTemplate="{Binding template}" if your control (that you wish to dynamically modify its contents - e.g. Button inside etc.) is ContentControl. I found that often 'overlooked' as it's not immediately visible or intuitive, but saves you adding extra 'content'.
Or you can use ContentControl - or presenter as suggested already.
<ContentPresenter ContentTemplate="{Binding MyTemplate}"/>

Resources