WPF Complex Hierarchical data template - c#

I'm looking at a complex structure and I can't seem to find a way to display it...
My situation:
Class: Milestone has 2 lists inside, a list of other submilestones and a list of activities.
So structured could be like:
M1
Milestones
SubMilestone
Milestones
Activities
Submilestone
Activities
Anybody has any idea's on how to create this? or could boost me into a direction?
ANSWER to my problem
<TreeView ItemsSource="{Binding Path=Project.TrackList}">
<TreeView.Resources>
<HierarchicalDataTemplate DataType="{x:Type deto:Track}" ItemsSource="{Binding Path=FaseList}">
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Path=TrackType}" />
</StackPanel>
</HierarchicalDataTemplate>
<HierarchicalDataTemplate DataType="{x:Type deto:Fase}" ItemsSource="{Binding Path=MilestoneList}">
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Path=FaseType}" />
</StackPanel>
</HierarchicalDataTemplate>
<HierarchicalDataTemplate DataType="{x:Type deto:Milestone}" ItemsSource="{Binding Converter={StaticResource MConverter}}">
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Path=Description}" />
</StackPanel>
</HierarchicalDataTemplate>
<HierarchicalDataTemplate DataType="{x:Type deto:Activity}" ItemsSource="{Binding Path=ActivityList}">
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Path=Description}" />
</StackPanel>
</HierarchicalDataTemplate>
</TreeView.Resources>
</TreeView>
And the converter:
public class MilestoneConverter : IValueConverter
{
#region IValueConverter Members
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
var m = value as Milestone;
CompositeCollection collection = new CompositeCollection();
collection.Add(new CollectionContainer()
{
Collection = m.MilestoneList
});
collection.Add(new CollectionContainer()
{
Collection = m.ActivityList
});
return collection;
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
#endregion
}

You should be able to do this using a CompositeCollection for example. Doing it in Xaml could be bit complicated in terms of referencing the sources, but using a converter should be acceptable in this case:
public class MilestoneItemsSourceCreator : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
var input = value as Milestone;
CompositeCollection collection = new CompositeCollection();
collection.Add(new CollectionContainer(){ Collection = input.SubMilestones });
collection.Add(new CollectionContainer(){ Collection = input.Activities });
return collection;
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotSupportedException();
}
}
<vc:MilestoneItemsSourceCreator x:Key="MilestoneItemsSourceCreator"/>
<HierarchicalDataTemplate DataType="{x:Type local:Milestone}"
ItemsSource="{Binding Converter={StaticResource MilestoneItemsSourceCreator}}">
<!-- DataTemplate -->
</HierarchicalDataTemplate>
This might not completely fit your class structures but you did not post those explicitly, some adjustments might be needed.

I've done something similar in the past.
You should display two list controls (ListView/ListBox for example) one above the other one, and bind the second's Data to the first one's selected item.
In your case, it seems you'd need 3 list controls, adapt as you please.

Related

WPF bind enum to combobox

In my WPF UserControl I need to bind an Enum on a ComboBox. This enum is declared locally:
public partial class ViewerDataConfiguration : UserControl
{
private ViewerDataConfigurationViewModel PageViewModel;
public Visibility IsParametriSelected { get; set; }
public IEnumerable<eDatoAlarmMode> EnumAlarmModes {
get
{
return Enum.GetValues(typeof(eDatoAlarmMode)).Cast<eDatoAlarmMode>();
}
}
On the main Grid, where there is a collection bound, I defined a ComboBox as follows:
<TextBox Grid.Column="16" Text="{Binding ConfigObject.Edit.Source}" Style="{StaticResource txtDataStyle2}" Width="30" Visibility="{Binding ConfigObject.Edit, Converter={StaticResource ListaValoriVisibilityConverter}}" HorizontalAlignment="Stretch" TextChanged="Data_TextChanged" />
<Label Grid.Column="17" Content="AlarmMode" Style="{StaticResource labelStyle2}" />
<ComboBox Grid.Column="18" Width="30"
ItemsSource="{Binding Path=EnumAlarmModes, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=local:ViewerDataConfiguration}}"
DisplayMemberPath="Value"
SelectedValuePath="Value" Style="{StaticResource comboUsersStyle}" />
Basically seems that my IEnumerable is not bound correctly. I see the elements but they're blank. Any hint?
You are using DisplayMemberPath and SelectedValuePath attributes, but your collection item type is just a simple string, and want to use this entire instance directly, so you should remove these attributes and it should work as expected.
You also need to change the field to a property as data binding works only on properties, not class fields (although x:Bind in UWP no longer has this limitation):
public IEnumerable<AlarmMode> EnumAlarmModes
{
get
{
return Enum.GetValues(typeof(AlarmMode)).Cast<AlarmMode>();
}
}
If you want to display enum values instead of names, create a value converter:
public class EnumValueConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
return (int)value;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
And then use it in the ItemTemplate:
<ComboBox Grid.Column="18" Width="100"
ItemsSource="{Binding Path=EnumAlarmModes}">
<ComboBox.Resources>
<local:EnumValueConverter x:Key="EnumValueConverter" />
</ComboBox.Resources>
<ComboBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Converter={StaticResource EnumValueConverter}}" />
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
For this to work you also have to add a xmlns:local declaration to your Window:
xmlns:local="clr-namespace:NamespaceOfConverter"

Using a IConverter to deal with {NewItemPlaceholder} in WPF / XAML / MVVM

Here is my DataTemplate:
<UserControl.Resources>
<converter:PlaceholderConverter x:Key="_placeholderConverter"/>
<!-- Data(Display)Template for data objects of x:Type Customer-->
<DataTemplate DataType="{x:Type model:Customer}">
<!-- Customer Properties will be vertically stacked -->
<ContentControl >
<StackPanel>
<TextBlock Text="{Binding FirstName}"/>
<TextBlock Text="{Binding LastName}"/>
<TextBlock Text="{Binding Phone}"/>
</StackPanel>
</ContentControl>
</DataTemplate>
<UserControl.Resources>
And the two different 'container's:
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="25"/>
<RowDefinition Height="100"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<Button Grid.Row="0"
Content="Delete"
HorizontalAlignment="Left"
VerticalAlignment="Top"
Width="75"
Command="{Binding DeleteCommand}"/>
<DataGrid Grid.Row="1"
ItemsSource="{Binding Customers}"
SelectedItem="{Binding SelectedCustomer}"
AutoGenerateColumns="True"/>
<ListBox
Grid.Row="2"
ItemsSource="{Binding Customers, Mode=OneWay}"/>
</Grid>
And the app:
How to remove the {NewItemPlaceholder}? [Done, solution below].
How to prevent the binding error that mention "{NewItemPlaceholder}" when clicking in one of the empty rows in the table above intending on adding a new row (I can still add rows).
The errors:
...Cannot convert '{NewItemPlaceholder}' from type 'NamedObject' to type 'CustomerExample.Model.Customer'...
...ConvertBack cannot convert value '{NewItemPlaceholder}' (type 'NamedObject'). BindingExpression:Path=SelectedCustomer; DataItem='CustomerViewModel'...
I can write an IConverter implementation, but how to tie it in to the XAML?
thanks in advance :-)
Here is the implementation of the IConverter:
public class PlaceholderConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
if (value != null && value.ToString() == "{NewItemPlaceholder}")
return DependencyProperty.UnsetValue;
return value;
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
}
and to bind to individual items, the XAML goes something like:
<TextBlock Text="{Binding Name, Converter={StaticResource PlaceholderConverter}}"/>
But I think I need to add it 'globally' to the data collection elements, not to where individual properties are being bound.
You don't need a Binding Converter. Instead, bind the ListBox to a CollectionViewSource that wraps the Custumers collection. The CollectionViewSource skips the NewItemPlaceholder element from the source collection.
<UserControl.Resources>
...
<CollectionViewSource x:Key="CustomersCVS" Source="{Binding Customers}"/>
</UserControl.Resources>
...
<ListBox ItemsSource="{Binding Source={StaticResource CustomersCVS}}"/>
You also don't need a Converter for the SelectedItem Binding. Just set the Binding's TargetNullValue property:
<DataGrid SelectedItem="{Binding SelectedCustomer,
TargetNullValue={x:Static CollectionView.NewItemPlaceholder}}" .../>
Even though the accepted answer is correct for the OP's question I needed a more general approach to prevent the error and still allow users to add rows. As I used this ValueConverter for many different object types I used reflection to determine the target type's constructor and just returned a new object of that type.
public class IgnoreNewItemPlaceHolderConverter : IValueConverter
{
private const string NewItemPlaceholderName = "{NewItemPlaceholder}";
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if (value == DependencyProperty.UnsetValue)
return DependencyProperty.UnsetValue;
return value;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
if (value != null && value.ToString() == NewItemPlaceholderName)
{
var ctors = targetType.GetConstructors();
// invoke the first public constructor with no parameters.
return ctors[0].Invoke(new object[] { });
}
return value;
}
}

C# - WPF - Binding a List<int> to be printed in a StatusBar?

I have the following in xaml:
<StatusBarItem Content="{Binding CorrectGuesses}" Height="30" VerticalAlignment="Top" HorizontalContentAlignment="Center" />
Here, CorrectGuesses is a List<int>. I'd like the code to output the actual numbers in the list to the statusbar, but right now it only shows (Collection). What do I need to do so that the StatusBarItem's Content will be the items in the list? Thank you!
Add a Converter , so that it prints all the numbers.
namespace AppConverters
public class List2StringConverter : IValueConverter
{
public object Convert(object value, Type targetType,
object parameter, CultureInfo culture)
{
List<int> listNumbers = value as List<int>;
// separator : , - / ... or white space
return string.join("separator",listNumbers);
}
public object ConvertBack(object value, Type targetType,
object parameter, CultureInfo culture)
{
// To Do
}
}
in your xaml page add this line to your window tag as schema:
xmlns:app="clr-namespace:AppConverters"
also add :
<Window.Resources>
<app:List2StringConverter x:Key="L2StringConverter"/>
</Window.Resources>
then use it as follows :
<StatusBarItem Content="{Binding CorrectGuesses,Converter={StaticResource L2StringConverter}}" Height="30" VerticalAlignment="Top" HorizontalContentAlignment="Center" />
<StatusBarItem Height="30" VerticalAlignment="Top" HorizontalContentAlignment="Center">
<ItemsControl ItemsSource="{Binding CorrectGuesses}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Horizontal"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding}"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</StatusBarItem>

Using ObjectDataProvider inside HierarchicalDataTemplate

I want to add items of my class treeviewitem to a TreeView.
And I want to bind the ItemSource of this TreeViewItem to a method of itself !
I am trying to use the ObjectDataProvider for this.. See my XAML:
<Grid Background="#FFE5E5E5">
<Grid.Resources>
<HierarchicalDataTemplate DataType="{x:Type myNs:treeviewitem}">
<HierarchicalDataTemplate.Resources>
<ObjectDataProvider x:Key="getItems"
MethodName="GetItems"
ObjectInstance="{Binding RelativeSource={RelativeSource Self}}" />
</HierarchicalDataTemplate.Resources>
<HierarchicalDataTemplate.ItemsSource>
<Binding Source="{StaticResource getItems}" />
</HierarchicalDataTemplate.ItemsSource>
<StackPanel Orientation="Horizontal">
<TextBlock Margin="5,0,0,0"
Text="{Binding Name}" />
</StackPanel>
</HierarchicalDataTemplate>
</Grid.Resources>
<TreeView x:Name="guiTreeview"
HorizontalAlignment="Left"
Width="200" />
</Grid>
But binding to an ObjectInstance isnt possible!
How is it possible to get the current object instance "into" the ObjectDataProvider?
What would be the right way of doint this?
And NO, its not possible to use a Property ..
I have done it now with a ValueConverter.
XAML:
<Grid Background="#FFE5E5E5">
<Grid.Resources>
<HierarchicalDataTemplate DataType="{x:Type myNs:MyItem}" ItemsSource="{Binding RelativeSource={RelativeSource Self}, Converter={myNs:GetItemsConverter}}" >
<StackPanel Orientation="Horizontal">
<TextBlock Margin="5,0,0,0" Text="{Binding Name}" />
</StackPanel>
</HierarchicalDataTemplate>
</Grid.Resources>
<TreeView x:Name="guiTreeview" HorizontalAlignment="Left" Width="200" />
</Grid>
Converter:
public abstract class BaseConverter : MarkupExtension
{
public override object ProvideValue(IServiceProvider serviceProvider)
{
return this;
}
}
public class GetItemsConverter : BaseConverter, IValueConverter
{
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
var tvi = value as TreeViewItem;
if (tvi == null) return null;
var myitem = tvi.DataContext as MyItem;
if (myitem == null) return null;
return myitem.GetItems();
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
return null;
}
}

How to bind a combobox with converter to a list in WPF

Here I'm trying to bind the combobox to a List Codes. The combobox is displaying: A & B
<ComboBox ItemsSource="{Binding Path=Codes}"/>
public SettingsWindow()
{
InitializeComponent();
Codes = new List<Code> {Code.A, Code.B};
DataContext = this;
}
I have defined a converter to display a more understandable info in the combobox:
public class CodeConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
var code = (Code)value;
string text;
if (code == Code.A)
{
text = "ACI318-99";
}
else
{
text = "ACI318-11";
}
return text;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
return value;
}
}
But I don't know how to use this converter in my XAML, so that I will have ACI318-99 & ACI318-11 in my combobox.
You should set the ItemTemplate of your Combobox, and use the Converter inside that.
<ComboBox ItemsSource="{Binding Codes}">
<ComboBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Path=., Converter={StaticResource converterInstance}}"/>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
here, converterInstance should be an instance of your custom converter in a resource dictionary.
The Caliburn Micro convention is not that different but I just wanted to add it for future searchers. (Path=. is not needed in my case)
<ComboBox x:Name="MyPropertyWithItems">
<ComboBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Converter={StaticResource converterInstance}}"/>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
Try
<Window.Resources>
<CodeConverter x:Key="CodeConverter"/>
</Window.Resources>
and
<ComboBox ItemsSource="{Binding Path="Codes" Converter="{StaticResource CodeConverter}}"/>

Categories