WPF: Aggregating properties on a ListBox - c#

I have two ListBoxes, both use Extended SelectionMode. The ItemsSource of the first is a List, and uses a datatemplate. I'm trying to use an aggregation of some property from the first as the itemssource for the second. For example:
public class MultiAppPropertyAggregator : IValueConverter {
public object Convert(object value, Type targetType, object parameter, CultureInfo culture) {
IList<SomeObject> selectedItems = value as IList<SomeObject>;
Dictionary<string, string> bundles = new Dictionary<string,string>();
foreach(SomeObject myobj in selectedItems) {
foreach(KeyValuePair<string,string> name in myobj.Names) {
selectedItems.Add(name.Key, name.Value);
....
<ListBox x:Name="lstApplication" ItemsSource="{Binding}" SelectionChanged="lstApplication_SelectionChanged" SelectionMode="Extended" />
<ListBox x:Name="lstBundles" ItemsSource="{Binding ElementName=lstApplication,Path=SelectedItems,Mode=OneWay,Converter={StaticResource MultiAppPropertyAggregator}}" ItemTemplate="{StaticResource DictionaryList}" SelectedValuePath="Key" SelectionMode="Extended" />
So the objects in the first list contain a property of type Dictionary. I want to add all items in the dictionaries of all selected items in the first list to the second list.
The converter seems to be called on initial load, then not again after that and I end up with an empty second listbox. Am I missing something?

I'd guess that you're converter is only being called once because SelectedItems on a list box is not a DependencyProperty and, therefore, will not notify the binding that it has updated.
You may be better off doing this conversion in your codebehind/viewmodel (depending on which methodology you follow) and exposing a property for the second list box to bind to.
You can do this in one of two ways that I can think of. First, you can listen to SelectionChanged on the first list and update the property that the second list is bound to. Or, you can put an IsSelected property on the items that the first list is bound to and update your second list when that changes on any given item. You can add this style for ListBoxItem to sync the IsSelected property between the data item and the view:
<Style TargetType="{x:Type ListBoxItem}">
<Setter Property="IsSelected" Value="{Binding Path=IsSelected, Mode=TwoWay}"/>
</Style>
My guess is that the first one will be less difficult to implement, though it may not fully mesh with whatever UI methodology you're following.

Related

No Add Rows Operation allowed when a DataGrid binds to a list or observable collection of Double

I have an ObservableCollection<double> that is defined in my ViewModel.
ListWidthsFlat=new ObservableCollection<double>();
ListWidthsFlat.Add(120);
ListWidthsFlat.Add(200);
My XAML code :
<DataGrid ItemsSource="{Binding Path=ListWidthsFlat}" AutoGenerateColumns="False"
CanUserAddRows="True" CanUserDeleteRows="True" HorizontalAlignment="Center">
<DataGrid.Columns>
<DataGridTextColumn Header="{x:Static p:Resources.Width}" Binding="{Binding ., UpdateSourceTrigger=PropertyChanged}" Width="100" />
</DataGrid.Columns>
</DataGrid>
What I want is show the ObservableCollection, then offer possibility to add/delete items from my ObservableCollection<double>.
When I do the same thing on an ObservableCollection<T> all is working perfectly.
But when binding to ObservableCollection<double>, seems that parameters CanUserAddRows is not working.
Edit :
After additional tests, it seems the problem is that when I bind a DataGrid to an ObservableCollection<T>, and set CanUserAddRow=True, an additional empty line is automatically created (so I can edit it and add a new item to ObservableCollection.
When I bind a DataGrid to ObservableCollection<double>, no empty line is created.
Here is a screenshot to make it more understandable :
To properly do a CanUserAddRows the object list being bound to must implement IEditableCollectionView Interface which provide basic editing capabilities to the collection being bound to. Within that the item being presented from the list has to have a public default parameterless constructor.
Because a value type double does not have a constructor the grid detects that and does not provide an add row; hence you see the failure on double alone, but it works on the object, (class) instances of List<T> which have double as a specific property.
To work around the limitation,
Create a class which has a public parameterless constructor and one double property.
Then create your list of class and bind to that ObservableCollection (or List works too actually if you don't need the overhead of the observablecollection.) with a set of your values.
In Xaml set the column in the datagrid to point to the double property.
You may need to write a value constructor which will take in a float and return a string, and convert a string to a float.

Binding RadioButtons to choose one item from a collection displayed in a ListBox

I have an ObservableCollection in my Model, displayed in a ListBox in my View. Each ListBoxItem displays a radio button which should allow the user to choose one of the items. A property of the ViewModel should record this choice by holding a reference to the chosen item.
How can I set up a two-way binding to do this?
An IValueConverter should allow me to bind RadioButton.IsChecked to the VM property, with the actual item in which the radio button occurs passed to the converter either as a parameter or a value in an IMultiValueConverter. This way I can:
Return true / false for Convert() based on comparison of the VM property and the item the radio button is associated with.
Return the item if IsChecked==true and Binding.DoNothing otherwise for ConvertBack().
However, ValueConverter parameters cannot be bound to because they aren’t dependency properties (so I can't bind to the item to use as a ValueConverter parameter) and I cannot use the MultiConverter because although Convert() will receive both values of interest, ConvertBack() will only receive the value of IsChecked.
Notes
The built in ListBox selection mechanism is already in use for other purposes.
The collection of interest is nested in another collection and presented in a containing ListBox.
The ListBox is bound to a collection in the Model. I am hoping not to implement events in the Model collections that tell the VM how to record events in the View, for obvious reasons.
I managed to solve this:
Include a converter on the resources node of the ItemsPanel which wraps each ListBoxItem (or somewhere else within the repeated part of the tree), so there is one converter instance per item.
Add a property (Host in my example) to the converter for the item each converter attaches to and initialise it somehow* as each ListBoxItem is created.
Bind IsChecked to the ViewModel property you need to have populated using the converter.
*I tried the Host property as a DP with binding to the DataContext (which should be one member of the collection the ListBox is bound to at the point at which the converter is located), but I simply could not get this to work for my nested ListBoxes. I resorted to the Initialized event of the containing element and some code behind (which did allow Host to revert to an ordinary property; the DP was overkill but had been necessary for binding).
The DataTemplate:
<StackPanel Initialized="StackPanel_Initialized">
<StackPanel.Resources>
<!-- ExlcusionRadioConverter.Host is initialised in code behind -->
<local:ExclusionRadioConverter x:Key="ExclusionRadio" />
</StackPanel.Resources>
<RadioButton
GroupName="Exclusion"
IsChecked="{Binding RelativeSource={RelativeSource AncestorType={x:Type Window}, Mode=FindAncestor}, Converter={StaticResource ExclusionRadio}, Path=DataContext.ExclusionCriterion}" />
<Label Content="{Binding Description}" />
</StackPanel>
The Panel Initialized event (NB this answer originally had this code in the Loaded event handler, but this was causing some problems):
private void StackPanel_Initialized(object sender, EventArgs e)
{
StackPanel panel = (StackPanel)sender;
ExclusionRadioConverter converter = (ExclusionRadioConverter)panel.FindResource("ExclusionRadio");
converter.Host = panel.DataContext as OptionListMember;
}
The Converter:
[ValueConversion(typeof(object), typeof(bool))]
public class ExclusionRadioConverter : IValueConverter
{
public OptionListMember Host { get; set; }
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
return ReferenceEquals(value, Host);
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
return ((bool)value) ? Host : Binding.DoNothing;
}
}

Transforming a TreeView to Multiple ListBoxes

Given a hierarchical data structure of the form:
public class MyData
{
public string Description { get; set; }
public IEnumerable<MyData> SubNodes { get; set; }
}
I would like to display a series of ListBoxes representing that structure. The flow should be left --> right (e.g. like OS X's Finder), where the left-most ListBox contains the root nodes and the right-most the children.
Multiple items in each ListBox should be selectable, causing the available items in subsequent ListBoxes to update. This is trivial to do with a bit of LINQ and a hard-coded number of ListBoxes, however I wanted the template to be dynamic (i.e. ListBoxes should be added and removed depending on the availability of items). I'd also like the solution to be MVVM-compatible.
This type of control might be something that is already bundled in WPF, however I'm not sure what to search for! Any pointers would be appreciated.
First of all, you do not need to use LINQ (or any C# code at all!) to set the ItemSource on the next ListBox. You can use WPF databinding for this:
<ListBox x:Name="listbox1" ItemsSource="{Binding Path=YourDataCollection}"/>
<ListBox x:Name="listbox2" ItemsSource="{Binding ElementName=listbox1, Path=SelectedItem.SubNodes}" />
<ListBox x:Name="listbox3" ItemsSource="{Binding ElementName=listbox2, Path=SelectedItem.SubNodes}" />
Basically you are binding the next listbox to the previous listbox's SelectedItem's SubNodes (you probably should make sure that the listbox can only select one at a time)
Now to hide the listboxes that do not have any items, you could use a IValueConverter to convert the object to a Visibility state. To do this, create a class:
public class ObjectToVisibilityConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
if (value != null)
return Visibility.Visible;
else
return Visibility.Collapsed;
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
}
And add some more data binding to your listboxes:
<Window.Resources>
<local:ObjectToVisibilityConverter x:Key="objectToVisible" />
</Window.Resources>
<Grid>
<ListBox x:Name="listbox1" ItemsSource="{Binding ElementName=mw1, Path=dataCollection}"/>
<ListBox x:Name="listbox2" ItemsSource="{Binding ElementName=listbox1, Path=SelectedItem.SubNodes}"
Visibility="{Binding ElementName=listbox2, Path=ItemsSource, Converter={StaticResource objectToVisible}}" />
<ListBox x:Name="listbox3" ItemsSource="{Binding ElementName=listbox2, Path=SelectedItem.SubNodes}"
Visibility="{Binding ElementName=listbox3, Path=ItemsSource, Converter={StaticResource objectToVisible}}" />
</Grid>
Make sure to add your namespace to the window, so the valueconverter can actually be found, in my case I added this:
xmlns:local="clr-namespace:WpfApplication1"
Now this solution works only for a fixed amount of listboxes, but it does not rely on any C# code, aside from the valueconverter. Do you really have no idea about how many sublevels you might need? You could use this technique to add dozens of listboxes to a scrollview for example.
To actually make this dynamic in a UserControl might become quite painful as there would be significant amount of code involved to add listboxes, set their items, delete or hide listboxes when a different root node is selected.
Another possibility might be creating a HierarchicalDataTemplate for the TreeView to customize the look of the treeview. This has the advantage of keeping the treeview, as it is the most useful control for hierarchical data, but changing it's looks should be entirely possible with WPF and it's templates. You might need to change the treeview's controltemplate though (I recommend Blend for editing any type of WPF template)

Bind textblock to dictionary value for key in XAML?

I have a model with an enum property (in this case, related to Export Control Regulations). When displaying the value to the user, I want to show a corresponding string. Sometimes this is in ComboBox (where the user can select a value), and sometimes it is in a TextBlock (where it is read-only).
Example: for ExportRegulationType.EAR, I want to display "EAR", while for ExportRegulationType.DoNotExport, I want to display "Do Not Export". Note that I don't have any language localization needs, but I recognize the issue...
Currently, in my ViewModel, I have a property that returns a string based on the current enum value, and also another property that returns a Dictionary<ExportRegulationType, string>. For the ComboBoxes, I can bind ItemsSource to the dictionary property, and for the TextBlocks, I can bind to the string property. This works, but is kind of clumsy.
Two questions:
1) It seems to me that I should be able to declare the dictionary (with keys and values) as a static resource in XAML (probably in App.xaml), and use that for the ItemsSource for the ComboBox version. However, I can't figure out how to declare and reference such a thing. How can I do that?
2) Assuming the above is in place, I would think I could also set up a binding with the textblock, so based on the enum property, it will look up the string in the dictionary.
I have seen the following questions relating to a static or dynamic enum value. The first isn't adequate, and the second isn't answered...
These should be a XAML-only, and will enable me to remove the methods from my ViewModel (having only the one exposed ExportRegulationType enumerated property. Are these possible?
Edit: Additional information:
In the application, I will have many different sets of views, models, and ViewModels. However, as export control regulations are a common and consistent requirement, I am using composition to keep it DRY. i.e., Models A and B both have an ExportControl model. ViewModels A1, A2, B1 and B2 will have an ExportControlViewModel. The views will have controls bound to the ExportControlViewModel of their ViewModel. The views will have either a ComboBox or a TextBlock, but not both (Depending on if the user can change the value).
I don't know if this will work for your case, but here is a possible solution. In your view model, expose a ExportRegulationType property and then create a value converter to display your desired string.
First create your value converter:
class ExportRegulationTypeToStringConverter: IValueConverter
{
#region IValueConverter Members
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
ExportRegulationType regType = (ExportRegulationType)value;
switch(regType)
{
case ExportRegulationType.EAR:
return "EAR";
case ExportRegulationType.DoNotExport:
return "Do Not Export";
//handle other cases
}
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
return Binding.DoNothing;
}
#endregion
}
Then add a reference to your converter in your xaml. local is the namespace in which your class is located.
<local:ExportRegulationTypeToStringConverter x:Key="exportRegConverter" />
Finally, set the value of your text box to use the converter. pathToEnum is the property exposed on your ViewModel of type ExportRegulationType.
<TextBlock Text="{Binding pathToEnum, Converter={StaticResource exportRegConverter}}" />
Use ObjectDataProvider to fill the ComboBox with the values of the enum.
<Window.Resources>
<ObjectDataProvider x:Key="dataFromEnum"
MethodName="GetValues" ObjectType="{x:Type System:Enum}">
<ObjectDataProvider.MethodParameters>
<x:Type TypeName="local:ExportRegulationType"/>
</ObjectDataProvider.MethodParameters>
</ObjectDataProvider>
</Window.Resources>
Now we create the ComboBox and use a container style with our value converter to display the desired strings for our enum.
<ComboBox ItemsSource="{Binding Source={StaticResource dataFromEnum}}">
<ComboBox.ItemContainerStyle>
<Style TargetType="ComboBoxItem">
<Setter Property="Content" Value="{Binding Converter={StaticResource exportRegConverter}}" />
</Style>
</ComboBox.ItemContainerStyle>
</ComboBox>
Instead of the Dictionary you have another option.
See the following question: WPF Binding a ListBox to an enum, displaying the Description Attribute
You could add a Description Attribute to your enums like this
public enum ExportRegulationType
{
[Description("EAR")]
EAR,
[Description("Do Not Export")]
DoNotExport
}
And when you want to display it, you can just use EnumDescriptionConverter Converter found in the question I linked
I solved this with a blend of what #Dylan and #Meleak wrote. I'm putting this as an answer to show what the final solution was:
First, I implemented an IValueConverter, (based on #Meleak's answer):
class EnumDescriptionConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
Enum regulation = (Enum)value;
return GetEnumDescription(regulation);
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
return String.Empty;
}
/// <summary>
/// Returns text intended for display based on the Description Attribute of the enumeration value.
/// If no Description Attribute is applied, the value is converted to a string and returned.
/// </summary>
/// <param name="enumObj">The enumeration value to be converted.</param>
/// <returns>Text of the Description Attribute or the Enumeration itself converted to string.</returns>
private string GetEnumDescription(Enum enumObj)
{
// Get the DescriptionAttribute of the enum value.
FieldInfo fieldInfo = enumObj.GetType().GetField(enumObj.ToString());
object[] attributeArray = fieldInfo.GetCustomAttributes(typeof(DescriptionAttribute), false);
if (attributeArray.Length == 0)
{
// If no Description Attribute was found, default to enum value conversion.
return enumObj.ToString();
}
else
{
// Get the text of the Description Attribute
DescriptionAttribute attrib = attributeArray[0] as DescriptionAttribute;
return attrib.Description;
}
}
}
I tagged my enum (note that several values are not tagged as the desired text is the same as the value itself):
public enum ExportRegulationType
{
[Description("Not Determined")]
NotDetermined, // Export authority not determined
EAR, // Controlled by EAR Regulations
ITAR, // Controlled by ITAR Regulations
[Description("Do Not Export")]
DoNotExport, // Export not allowed
Unrestricted // Export not controlled
}
In my App.xaml, I declared the ObjectDataProvider to get the list of enum values and the EnumDisplayConverter (Here since they will be used by several different views):
<Application.Resources>
[Other stuff...]
<ObjectDataProvider MethodName="GetValues"
ObjectType="{x:Type sys:Enum}"
x:Key="ExportRegulationValues">
<ObjectDataProvider.MethodParameters>
<x:Type TypeName="models:ExportRegulationType"/>
</ObjectDataProvider.MethodParameters>
</ObjectDataProvider>
<local:EnumDescriptionConverter x:Key="ExportDisplayConverter"/>
</Application.Resources>
For a TextBlock:
<TextBlock Text="{Binding Export.Regulation, Converter={StaticResource ExportDisplayConverter}}"/>
For a Combo Box:
<ComboBox ItemsSource="{Binding Source={StaticResource ExportRegulationValues}}"
SelectedValue="{Binding Document.Export.Regulation}">
<ComboBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Converter={StaticResource ExportDisplayConverter}}"/>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
This works perfectly!
Use an ObjectDataProvider
then bind the ComboBox's Items to it, and set "DisplayMemberPath" to "Value".
What this should do is to show the values of your dictionary, but in code-behind the SelectedValue is a KeyValuePair<>.
For your textblock, use a Binding using ElementName=yourcombobox and Path=SelectedItem:
<TextBlock Text="{Binding SelectedItem, ElementName=yourcombobox}" />
Let me know how it goes =)
Here is a blog post of mine with an approach using attached behaviors.
It is based on the principle that different enumeration values don't need to be limited to switching strings. Instead, you can declare whatever pieces of UI you want to represent each value (strings, images, different controls and layouts, etc.) and use an attached behavior to control their visibility.
Your situation, then, can be framed as having two different text blocks, each bound to the same property of type ExportRegulationType. Since they are bound to the same property, their visibilities are mutually exclusive:
<Grid>
<TextBlock
Text="EAR"
local:EnumVisibility.Value="{Binding ExportRegulationType}"
local:EnumVisibility.TargetValue="EAR"
/>
<TextBlock
Text="Do Not Export"
local:EnumVisibility.Value="{Binding ExportRegulationType}"
local:EnumVisibility.TargetValue="DoNotExport"
FontWeight="Bold"
/>
</Grid>
I included the FontWeight="Bold" to show that you can make different decisions for each enumeration value. This also supports XAML localization because the text is set like any other text block.
See the post for a complete walkthrough of the solution, code samples, and a zip file containing the framework and an example application.
Edit in response to additional information:
Here is another post in the same series which describes how to select enumeration values with Selector controls.
A ComboBox bound to the ExportRegulationType property would look this this:
<ComboBox local:EnumSelector.SelectedValue="{Binding ExportRegulationType, Mode=TwoWay}">
<ComboBoxItem Content="EAR" local:EnumSelector.ItemValue="EAR" />
<ComboBoxItem Content="Do Not Export" local:EnumSelector.ItemValue="DoNotExport" />
</ComboBox>
We associate each item with an enumeration value, then use a TwoWay binding to EnumSelector.SelectedValue so it will write back to the view model's property whenever it changes.
This provides the same flexibility as with the text blocks: you can make whatever decisions you want about how to set the text and what is contained by each item.

Find a control within a ListViewItem

I have a ListView displaying a list of items containing mainly two properties.
Each of these properties should ideally be chosen from two comboboxes.
Moreover, the choices available in the second combobox is depends on the first.
So here is the idea of the code I used:
<ListView>
<ListView.ItemTemplate>
<DataTemplate>
<StackPanel>
<ComboBox Name="combo1"
ItemsSource="{DynamicResource combo1Source}"
SelectedItem="{Binding FirstProperty}"
SelectionChanged="combo_SelectionChanged">
<ComboBox Name="combo2"
ItemsSource="{DynamicResource combo2Source}"
SelectedItem="{Binding SecondProperty}">
</StackPanel>
<DataTemplate>
<ListView.ItemTemplate>
</ListView>
The thing is, I don't know how to get the reference to combo2 from within combo_SelectionChanged (in C#).
Could you show me how to proceed?
The easiest thing you can do is add a Tag to combo1:
<ComboBox Name="combo1" Tag="{x:Reference combo2}" ... />
Which you then can just get from the sender in the event handler, e.g.
var combo2 = (sender as FrameworkElement).Tag as ComboBox;
Alternatively you could get the StackPanel from the Parent property and just take (ComboBox)Children[1]. I would not do this though as is breaks if the structure of your template changes.
You should not have a reference to combo2, but you should update the Collection combo2Source which is bound as ItemsSource for combo2...
So in the combo_SelectionChanged you just load the possible values for the actual selection of combo1 to the combo2Source Collection.
EDIT: To prevent thats its for all items the same:
Add a ValueConverter which choses for a selectedItem the corresponding collection of possible values:
<ComboBox ItemsSource="{Binding ElementName=Combo1, Path=SelectedItem, Converter={StaticResource SubSelectionConverter}}" />
Example of ValueConverter:
private Dictionary<Object, List<Object>> _PossibleValues;
public object Convert(Object data, ....)
{
if(PossibleValues.ContainsKey(data))
{
//return the possible values for the actual selected parent item
return(PossibleValues(data));
}
return null;
}
Can have look here on my question and different responses and the solution I found for my specific project:
Find an element in Data Template
Hope this helps.
Regards.

Categories

Resources