In my application I have the following which works fine.
<ListView ItemsSource="{Binding Items}">
<ListView.ItemTemplate>
<DataTemplate>
<StackPanel Margin="0,0,0,27.5">
<TextBlock Text="{Binding Name}" />
</StackPanel>
</DataTemplate>
</ListView.ItemTemplate>
But now I want to bind a different sort of Items which have no Name property, but a Title property.
I've tried the following:
<TextBlock Text="{Binding {Binding MyDynamicProperty}}" />
So that I could set MyDynamicProperty in code to either Name or Title, but it crashes pretty soon.
Now I'm not really sure how I should go about this so that I can bind several different Items collections with a different property for the text to display.
Related
I have a ListBox in my WPF MVVM app using the following code:
<GroupBox Grid.Column="0" Margin="0,0,0,-58">
<DockPanel>
<TextBlock DockPanel.Dock="Top"
HorizontalAlignment="Center"
Margin="0,0,0,8"
FontWeight="Bold"
Text="{x:Static p:Resources.AvaliableLEDsLabel}" />
<ListBox Name="AvailableLEDsListbox" SelectionMode="Extended"
dd:DragDrop.IsDragSource="True"
dd:DragDrop.IsDropTarget="True"
dd:DragDrop.DropHandler="{Binding}"
ItemTemplate="{StaticResource DataTemplateListBoxItem}"
ItemsSource="{Binding AvailableLeds}"
ScrollViewer.VerticalScrollBarVisibility="Hidden"
>
<ListBox.GroupStyle>
<StaticResource ResourceKey="StyleListBoxGroup" />
</ListBox.GroupStyle>
</ListBox>
</DockPanel>
</GroupBox>
This displays grouped lists of devices, with LEDs under them. The DataTemplate is the following:
<GroupStyle x:Key="StyleListBoxGroup">
<GroupStyle.HeaderTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<Button Command="{Binding HideGroupCommand}">X</Button>
<TextBlock VerticalAlignment="Center"
HorizontalAlignment="Left"
FontWeight="Bold"
Text="{Binding Name}" />
</StackPanel>
</DataTemplate>
</GroupStyle.HeaderTemplate>
</GroupStyle>
<DataTemplate x:Key="DataTemplateListBoxItem">
<TextBlock x:Name="LedId" Text="{Binding LedId}"/>
</DataTemplate>
I would like to make the X button in the header hooked up to the HideGroupCommand toggle the hiding of all the items under that particular header. How would I go about doing this? Thanks in advance.
You have few options.
First one :
You would need to have a property in your view model something like 'ListBoxVisibility' then u would bind that property to your UI. In command text u just changed visibility property of that property in view model- so u have it reflected on UI. This visibility property can be of type 'bool' , or 'Visibility' or whatever. Only if it's type of Visibility u don't need converter when binding.
NOTE : Some people use it - even though it kinda goes out of general principel of MVVM patter. But sometimes u have to do it.
Second
If wanna stick to MVVM , then u need to fully separate your UI from your viewmodel. Create click event and change visibility.
I have ListBox that has binding to the collection (Publishers), every publisher has a name and collection of authors in it, every author has a name. So how can I provide binding to author's name?
<TextBlock Text="{Binding name}" Grid.Column="1" Grid.Row="1"></TextBlock>
<TextBlock Text="{Binding city}" Grid.Column="1" Grid.Row="0"></TextBlock>
It is binding to a publisher's name and city.
Something like this provided that the Publisher class, or whatever you call it, exposes the authors using a public collection property that has an indexer:
<ListBox x:Name="listBox" ItemsSource="{Binding Publishers}" .... />
<TextBlock Text="{Binding SelectedItem.Authors[0].Name, ElementName=listBox}" Grid.Column="1" Grid.Row="0"></TextBlock>
This will the display the name of the first author at index 0.
If you intend to display all authors of the selected publisher, you should use an ItemsControl:
<ItemsControl ItemsSource="{Binding SelectedItem.Authors, ElementName=listBox}"
Grid.Column="1" Grid.Row="0">
<ItemsControl.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Name}"></TextBlock>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
<ListBox ItemsSource="{Binding}">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel>
<TextBlock Text="{Binding name}"></TextBlock>
<ListBox ItemsSource="{Binding authors}">
<ListBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding name}"></TextBlock>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
This will show name of a publisher with a list of all its authors for every publisher. I assumed a publisher has a property named authors that returns IEnumerable<Author>
An author would be an element in a collection which is part of a publisher.
So there is no way for WPF to 'know' which author's name to display: there are multiple.
Instead of a textblock, an ItemsControl could be used that binds its ItemsCollection to the collection of authors in the current publisher.
I'm trying to organize the items in a combobox into groups. To do this I've created an object that has project and group name strings. I then set the GroupStyle and ItemTemplate to display these values. However, Currently, only the project string is displayed in the combobox (and the box has a red border, indicating some kind of error).
Here's the xaml for my combobox:
<ComboBox x:Name="comboBoxProjects" Margin="165,90,28,0" Grid.Column="0" VerticalAlignment="Top" Height="25"
IsSynchronizedWithCurrentItem="True" SelectedIndex="0" Style="{StaticResource ComboBoxDefault}"
ItemsSource="{Binding Path=ProjectClientSelections.ProjectGroupItems,Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
SelectedItem="{Binding Path=ProjectClientSelections.SelectedProject, UpdateSourceTrigger=PropertyChanged}">
<ComboBox.GroupStyle>
<GroupStyle>
<GroupStyle.HeaderTemplate>
<DataTemplate>
<TextBlock Text="{Binding GroupName}"/>
</DataTemplate>
</GroupStyle.HeaderTemplate>
</GroupStyle>
</ComboBox.GroupStyle>
<ComboBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Project}"/>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
Does anyone see where I'm going wrong?
In GroupStyle, the DataContext is not your item (the type contained in your ItemsSource), but a CollectionViewGroup object, which is formed based on the collection of items that you have grouped. Because of this you have to declare a binding path to one of the properties in CollectionViewGroup, for example, based on your code you probably want to use Name property. See MSDN CollectionViewGroup Class
Change your GroupStyle.HeaderTemplate to this:
<DataTemplate>
<TextBlock Text="{Binding Name}" />
</DataTemplate>
You don't show how you have formed your GroupDescriptions. If you have not grouped the items already, you can do it in following way (assuming the XAML you have provided is contained inside Window and Window's and GroupBox's DataContext is the same):
<Window.Resources>
<CollectionViewSource
Source="{Binding ProjectClientSelections.ProjectGroupItems}"
x:Key="GroupedProjectItems">
<CollectionViewSource.GroupDescriptions>
<PropertyGroupDescription
PropertyName="GroupName" />
</CollectionViewSource.GroupDescriptions>
</CollectionViewSource>
</Window.Resources>
After this change GroupBox ItemSource binding to the following (directly to CollectionViewSource resource):
ItemsSource="{Binding Source={StaticResource GroupedProjectItems}}"
I have a TreeView that looks like this:
<TreeView Grid.Row="1" x:Name="InspectionResultsTreeView"
ItemsSource="{Binding Source={StaticResource InspectionTypeGroupViewSource}, Path=Groups}"
ItemTemplate="{StaticResource InspectionTypeGroupsTemplate}">
</TreeView>
The ItemsSource is a keyed resource that goes by the name of InspectionTypeGroupViewSource:
<CollectionViewSource x:Key="InspectionTypeGroupViewSource" Source="{Binding Results}">
<CollectionViewSource.GroupDescriptions>
<PropertyGroupDescription PropertyName="Inspection.InspectionType" />
<PropertyGroupDescription PropertyName="Inspection" />
</CollectionViewSource.GroupDescriptions>
</CollectionViewSource>
The role of this little thing is to take the ViewModel's Results property:
private ObservableCollection<ICodeInspectionResult> _results;
public ObservableCollection<ICodeInspectionResult> Results
{
get { return _results; }
set { _results = value; OnPropertyChanged(); }
}
...and group it on two levels - first by InspectionType, then by Inspection - the result is a 3-level hierarchy with inspection types, inspections, and then individual inspection results. At this point a screenshot might help visualizing I guess:
So, the ItemTemplate of the InspectionResultsTreeView is another keyed resource, by the name of InspectionTypeGroupsTemplate - that's the bold "inspection type" items:
<HierarchicalDataTemplate x:Key="InspectionTypeGroupsTemplate"
DataType="{x:Type CollectionViewGroup}"
ItemsSource="{Binding Items}"
ItemTemplate="{StaticResource InspectionGroupsTemplate}">
<StackPanel Orientation="Horizontal">
<TextBlock VerticalAlignment="Center"
Text="{Binding Name}"
FontWeight="Bold"
TextWrapping="NoWrap"/>
<TextBlock Margin="4,0,4,0"
VerticalAlignment="Center"
Text="{Binding ItemCount, StringFormat=({0})}"
TextWrapping="NoWrap"/>
</StackPanel>
</HierarchicalDataTemplate>
And the ItemTemplate of that template is an InspectionGroupsTemplate - that's the individual inspections, with the "severity" icons:
<HierarchicalDataTemplate x:Key="InspectionGroupsTemplate"
DataType="{x:Type CollectionViewGroup}"
ItemsSource="{Binding Items}"
ItemTemplate="{StaticResource InspectionResultTemplate}">
<StackPanel Orientation="Horizontal">
<Image Style="{StaticResource IconStyle}"
Source="{Binding Name, Converter={StaticResource InspectionIconConverter}}"
VerticalAlignment="Center" />
<TextBlock Margin="4"
VerticalAlignment="Center"
Text="{Binding Name, Converter={StaticResource InspectionDescriptionConverter}}"
TextWrapping="NoWrap"/>
<TextBlock Margin="0,4,0,4"
VerticalAlignment="Center"
Text="{Binding ItemCount, StringFormat=({0})}"
TextWrapping="NoWrap"/>
</StackPanel>
</HierarchicalDataTemplate>
Lastly, the ItemTemplate of this grouping is an InspectionResultTemplate, which is for each individual inspection result:
<DataTemplate x:Key="InspectionResultTemplate"
DataType="{x:Type inspections:ICodeInspectionResult}">
<StackPanel Orientation="Horizontal">
<TextBlock VerticalAlignment="Center"
Margin="4"
Text="{Binding Name}"
TextWrapping="NoWrap"/>
</StackPanel>
</DataTemplate>
The ICodeInspectionResult interface has a string Name property that I'm using here; this Name is different from the Name that's used in the grouping levels, where it's an object CollectionViewGroup.Name - the underlying type of that Name is that of the grouping, so level 1 is an InspectionType, and level 2 is an Inspection.
The problem is that I'm using more converters than I believe I'd need to, to convert this object Name and access the members I need to access and display... but then, I need to display the number of items in each grouping so the DataType ought to be a CollectionViewGroup... right?
How can I do this without resorting to a converter for everything that needs to be displayed? How is this supposed to be done? Every TreeView / CollectionViewGroup tutorial I could find was a trivial implementation.
You've encountered the iconic problem with XAML: it's almost too structured.
The most idiomatic solution is writing a custom WPF User Control. (How and what you include in it is up you.) The goal of the WPF User Control is to eliminate the duplicate XAML markup and logic. You can include your Converter in the User Control, and eliminate the converters from your main control.
There are plenty of tutorials on creating UserControl objects in WPF, so I'll not go into detail here.
As far as the Converter issue: this is almost the most idiomatic way. Each converter is reusable, and focuses only on one source type. There's not much else you can do about it, except consider merging converters that support the same source type together. (There's a reason the converter has a Type targetType parameter, and an object parameter.)
I have some problems binding a ListBox to the elements of a collection in a collection.. Let me explain:
I have a collection, ObservableCollection<Test> named testsCollection. Every test contains an ObservableCollection<LogEvent> named LogEvents. Every LogEvent has a Message that I need to display in the ListBox.
I need to display every "Message" in every "LogEvent" in every "Test". It has to be displayed in a flat list so I'm using a ListBox.
Here's a summary of what I tried:
DataContext = testCollection; // testCollection is an ObservableCollection<Test>
The I put this in the XAML:
<ListBox ItemsSource="{Binding LogEvents}" ItemTemplate="{StaticResource stepItemTemplate}">
Finally, here's the ItemTemplate, stepItemTemplate:
<DataTemplate x:Key="stepItemTemplate">
<TextBlock Text="{Binding Message}"></TextBlock>
</DataTemplate>
This "works" but it only displays the Messages in the LogEvents of the first Test. But I need to display every Messages of every LogEvent of every Test.. And I don't know what to try anymore :(
You should Usser ItemsControl when you want to bind a scenario like this
<ListBox ItemsSource="{Binding testsCollection}">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel>
<TextBlock Text="{Binding Message}" FontSize="20" />
<ItemsControl ItemsSource="{Binding LogEvents}" Margin="0 20 0 0">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Border BorderBrush="Blue" BorderThickness="2">
<TextBlock Text="{Binding Message}" FontSize="20" />
</Border>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>