I have an XML file with the following structure:
<Products>
<Product name="MyProduct1">
<Components>
<Component name="MyComponent1">
<SubComponents>
<SubComponent name="MySubComponent1"/>
<SubComponent name="MySubComponent2"/>
...more SubComponent nodes...
</SubComponents>
</Component>
...more Component nodes...
</Components>
</Product>
...more Product nodes...
</Products>
I'm trying to create a WPF app that has a ComboBox with the Product names in it. I am completely new to WPF so I don't know if I'm going about things the right way. When a Product is chosen, a second ComboBox should be populated with all the Components for that Product. And when a Component is chosen, a third ComboBox should be populated with all the SubComponents for that Component.
I don't know how to set up a dependency between ComboBoxes except to populate the dependent ComboBox inside an event handler triggered by the independent ComboBox. This seems to imply I need to be able to read the XML in C#, so I have [Serializable] classes for Products, Product, Component, and SubComponent. However, I was trying to do XML databinding in my XAML:
<Window.Resources>
<XmlDataProvider Source="Products.xml"
XPath="Products/Product"
x:Key="productsXml"/>
</Window.Resources>
I'm currently not seeing a list of Product names in my first ComboBox, whose XAML is the following:
<ComboBox Height="23" HorizontalAlignment="Left" Margin="138,116,0,0"
Name="cbo_product" VerticalAlignment="Top" Width="120"
ItemsSource="{Binding Source=productsXml, XPath=#name}"
SelectionChanged="product_SelectionChanged"/>
The Products XML should be read-only--the user won't be able to change any values in the XML from the app. I just want to read the XML data and display it in the app.
I have a few questions:
Am I going about this correctly? Having a standalone XML file from which my WPF app reads, having [Serializable] classes representing the nodes in the XML file for the purpose of extracting data from those nodes in C#, using an event handler to code dependencies between ComboBoxes, etc.
Why don't my product names, e.g., MyProduct1, show up in my ComboBox? Currently it just shows up empty.
It seems almost like having [Serializable] classes for representing my XML nodes is redundant/unnecessary since XAML already has the XmlDataProvider/XPath stuff. Is this the case?
Edit:
Updated my ComboBox XAML to the following and now I see my list of Product names in the ComboBox, thanks to decyclone's answer:
<ComboBox Height="23" HorizontalAlignment="Left" Margin="138,116,0,0"
Name="cbo_product" VerticalAlignment="Top" Width="120"
ItemsSource="{Binding Source={StaticResource productsXml}}"
DisplayMemberPath="#name"
SelectionChanged="product_SelectionChanged"/>
The ItemsSource property should be set to a collection of items that are to be displayed in the list which is XmlDataProvider in your case. User StaticResource to locate it since it is defined as a resource. The DisplayMemberPath property should be used to select what property should be used to display text in combobox.
Regarding your 1st (& 3ed) question, I personally like create classes than using raw XML. It gives me few benefits like
Adding wrapper properties. For example, FullName = FirstName + " " + LastName property.
I dont have to check for empty values and type safety everytime I want to access the values (which are always strings).
I can add my own behaviors as methods which can be really useful for doing little tasks.
Control the serialization method and inject custom logic in it using attributes.
The point is, is it worth it? Do you really care for these benefits? It's just like choosing between DataReader and DataSet. For readonly and displayonly purpose, use raw XML and if you are going to play with it a lot, use classes.
Okay, since I found an answer to my more specific question, I think I know the answer to this question, too. I don't need [Serializable] classes for the different nodes in my XML, because I can just use XAML and XPath to create cascading/dependent ComboBoxes:
<!-- Independent -->
<ComboBox Height="23" HorizontalAlignment="Left" Margin="138,116,0,0"
Name="cbo_product" VerticalAlignment="Top" Width="120"
ItemsSource="{Binding Source={StaticResource productsXml}}"
DisplayMemberPath="#name"/>
<!-- Dependent -->
<ComboBox Height="23" HorizontalAlignment="Left" Margin="138,151,0,0"
Name="cbo_component" VerticalAlignment="Top" Width="201"
DataContext="{Binding ElementName=cbo_product, Path=SelectedItem}"
ItemsSource="{Binding XPath=Components/Component}"
DisplayMemberPath="#name"/>
Related
I am using PRISM to auto-wire my Views & ViewModels, however I have encountered a problem I cannot solve.
I am using a calendar control, which enables users to create new appointments via opening new modal window & saving it to calendar.
This window, is styled via a ControlTemplate, where I have the following item:
<telerik:RadComboBox Grid.Row="0" Grid.Column="1" Grid.ColumnSpan="2" Margin="3"
ItemsSource="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type UserControl}}, Path=DataContext.EmployeeList}">
Items Source of this combobox is the ViewModels DataContext.EmployeeList - ObservableCollection<Employee>.
This would work like a charm as long as it would not be a new pop-out window. That way, I believe it is a userControl as well and therefore my regular code does not recognize any EmployeeList.
There might be 2 ways how to solve it (I don't have direct access to the modal window as it is being automatically generated by the control itself - I am using Telerik suite).
1) Make sure that the ItemsSource will dig deeper than the very first UserControl that it finds. Maybe by slightly changing the code, it will be able to do so? (Maybe using something like AncestorLevel...?).
2) Telerik has shown an example of how to achieve that by the following line:
<local:ViewModel x:Key="ViewModel" /> -- define key first
ItemsSource="{Binding Source={StaticResource ViewModel}, Path=EmployeesSource}"...
BUT the issue with my ViewModel is that under constructor I am passing several interfaces like following:
private readonly IEmployeeRepository _employeeRepository;
public EmployeeView_HolidaysViewModel(IEmployeeRepository employeeRepository)
{
_employeeRepository = employeeRepository;
InitializeCollections();
InitializeCommands();
}
and therefore I can't make the above solution to work at all.
Any help with my problem would be highly appreciated. I simply need to get that list to that modal window's combobox.
In the end I managed to solve the problem by creating additional constructor to my class which looks like following:
public EmployeeView_HolidaysViewModel()
{
_employeeRepository = Microsoft.Practices.ServiceLocation.ServiceLocator.Current.GetInstance<IEmployeeRepository>();
InitializeCollections();
}
This way I can easily adopt Solution Nr 2 from the OP.
<press_limits value="1055" label="Press Limits" type="single 317" format="object">
<projected_area value="0.052944637336319995" label="Projected area of part" type="real(m*m)"/>
<press_tonnage value="500.0" label="Press tonnage" type="real(g)" units="0Ton"/>
<within_press_limit value="1" label="within limits of press" type="boolean"/>
From XML like the above, the XAML below will generate a form that displays the values, with appropriate controls and value formats. But I can't get the stuff converted back. I fail to see a simple change that will meet the requirements of Binding. Perhaps fixing this requires an architecture change. How can I do this differently?
The magic starts here. Bind this ItemsControl to an XmlElement, and it builds a ControlChooser for each subelement.
<ItemsControl ItemsSource="{Binding XPath=*}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate><WrapPanel/></ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<W3V:ControlChooser Content="{Binding}" />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
The ControlChooser triggers on the format attribute:
<Style.Triggers>
<DataTrigger Binding="{Binding XPath=#format}" Value="spin">
<Setter Property="ContentTemplate" Value="{StaticResource combo}" />
</DataTrigger>
to pick a DataTemplate:
<DataTemplate x:Key="combo" > <W3V:ComboView /> </DataTemplate>
which instatiates the following control:
<ComboBox Style="{StaticResource ComboButtonStyle}" Width="200"
Text="{Binding Path=., ## PROBLEM, BUT HOW TO AVOID?
Converter={StaticResource valueFormattingConverter },
IsEditable="True" />
valueFormattingConverter uses the #units, #type, and #value attributes to produce properly formatted text. The trouble is, this doesn't convert back. I asked here: TextBox ConvertBack event doesn't fire for XML element, and learned that it is impossible for Path=. to be used as '.' is an object but not a dependency property.
So then, I need a way to provide a DependencyProperty for Binding. So ComboView needs to receive an object that has a property which is or has the XmlElement I want. I think this means I really need to change things around but haven't the slightest idea how. Maybe there's an MVVM approach to doing this??? Any insights will be appreciated.
An upcoming issue is a need to validate the data typed in and process the information.
The MVVM method: pull the data out of the XML file, and put it a class which is the ViewModel. Each property in the ViewModel corresponds to an item of data in the XML file. Then use a custom DataTemplate to render the contents of the ViewModel to the screen.
We can extend this to render a list of items. Each item in the list is a ViewModel. A DataTemplate always renders based on the type of the property it is attached to. So you can have a list of objects, and a custom DataTemplate for each item in the list. Of course, all of the items in the list would have to inherit from the same type.
This means you can have a list of items, and each item can render differently depending on the type of data in the XML file. This means that each item in the list can have a custom look and feel with different number of decimal places, colors, etc.
I've used this technique before, and it works well.
Update
For examples, see:
http://www.wpftutorial.net/datatemplates.html
http://www.stackoverflow.com/questions/3400532/display-multiple-types-from-a-single-list-in-a-wpf-listbox.
If I was solving this, I would look at generating C# on the fly. Here is how I would do it:
All XAML compiles to a series of C# commands.
I would set the XAML to format things the way I like it.
I would find the equivalent C# code.
I would then insert the appropriate if/else statements to alter the C# to suit.
Another method that I would try:
Its possible to render custom XAML into an area on the screen.
I would edit the XAML, based on the XML, then display this custom XAML on the screen.
I'd be curious to know if either of these methods work in practice, or if there is a better method that would work.
I had the following XAML looking just as I want it to look. It's basically a dropdown menu with checkboxes and some text to them.
<ComboBox x:Name="comboBox">
<ComboBoxItem><CheckBox>WeeHee</CheckBox></ComboBoxItem>
<ComboBoxItem><CheckBox>BuuHuu</CheckBox></ComboBoxItem>
</ComboBox>
Then I learned that the options are possible to be dynamically adapted so I changed the markup and bound the combobox to an array. That works perfectly well functionality-wise but now I get only the names of the options, while the checkboxes are gone.
<ComboBox x:Name="comboBox"
ItemsSource="{x:Static local:MainWindow.AllKinds}">
</ComboBox>
So I'm looking for a way to create a custom item for populating the combobox but still so that the data is dynamically bound. I'm assuming WPF/XAML supports such functionality and I'm just too ignorant to know what to google for.
So my request is a rudimentary example or suggestion on what to google for. (Might be something seemingly obvious to everybody but me.)
So with most of the controls that offer up things from ItemSource there's a nifty built in feature that allows you to specify how each item should look/behave etc. So you can specify each Item in your ComboBox's by setting your own ItemTemplate.
Since a ComboBox just uses an ItemsControl to list its items, you can utilize this via ComboBox.ItemTemplate to make your items however you like, in this case probably something like (in pseudo).
<ComboBox>
<ComboBox.ItemTemplate>
<DataTemplate>
<CheckBox>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
Hope this helps, its some pretty handy info to know for when you want to start customizing Item generated lists between wpf, silverlight, wp, win-rt, etc. Cheers!
Here's the problem: I have a data-bound list of items, basically a way for users to map a request to a response. The response is an xml-based file. I'm letting them queue these up, so I've used a combobox for responses. The responses will include the full path, so they get a bit long. I want the displayed text of the combobox to be right-justified so the user can see the file name. For my static controls, I just use ScrollToHorizontalOffset() when a file is loaded and I'm done. For this dynamic list, I'd like to do it in xaml.
The "somewhat ugly" solution would be to store all the ComboBox objects as they load... then I can call ScrollToHorizontalOffset() directly, but I'd really prefer to do it a cleaner way than that! EDIT: (Actually, this may not be reasonable. A quick look at trying to hack around this issue gets into some really awkward situations trying to map my datasource items to the controls)
I've tried HorizontalContentAlignment, that only impacts the "dropped-down" portion of the ComboBox.
I've also tried to hook other various loading events, but haven't found one that works.
Using an Item template you can decide what will be shown.
You can set tooltip. You can then also use converters to add the dots.
<ComboBox x:Name="ConfigurationComboBox" VerticalContentAlignment="Center" ToolTip="saved configuration" SelectionChanged="ConfigurationComboBox_SelectionChanged">
<ComboBox.ItemTemplate>
<DataTemplate >
<StackPanel>
<TextBlock Text="{Binding}" ToolTip="{Binding Path}"></TextBlock>
</StackPanel>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
To measure the text, see Measuring text in WPF.
I'm using a static array of objects defined in XAML as my ItemsSource on a list box.
I use:
ItemsSource="{Binding Source={StaticResource theSource}}".
This works great at design time. However, I need to override this at run time so the list box will take it's items from a collection property on my view model. I exposed a Property Named "theSource" and set the Window DataContext in code behind. However, the list is still bound to the static resource... not to the property on my view model.
Any suggestion on the right way to have Design Time data for visualizing the UI and replace with live data at run time?
Just name your Listbox control
<ListBox x:Name="myListBox"
ItemsSource="{Binding Source={StaticResource theSource}}" />
and change it at runtime
myListBox.ItemsSource = datasource;
Second take at this problem:
What's the difference between StaticResource and DynamicResource in WPF?
If you're using WPF, then you might use a DynamicResource instead, and change the definition of this at runtime
It will fail with Silverlight, though, because this lighter framework doesn't support dynamic resources.
Third take
Since you want to use a binding to your controller object, then :
<Page xmlns="http://..." xmlns:your="...">
<Page.DataContext>
<your:DesignTimeObject> <!-- Must be of the same type as in runtime, or at least expose properties and subproperties with the same name -->
<your:DesignTimeObject.List>
<your:Element id="1" />
<your:Element id="2" />
<!-- snip -->
<your:DesignTimeObject.List>
</your:DesignTimeObject>
</Page.DataContext>
<ListBox x:Name="myListBox" ItemsSource="{Binding List}" /> <!-- Binds to the Element list with id="1" , id="2" ... -->
</Page>
With this way, you don't have to change your binding at runtime, set the DataContext once and be done with it.