Using multiple Treeview objects with the same (but cloned) itemssource - wpf

So, I have a listview with different datatemplates as seen here:
<ListView Panel.ZIndex="0" x:Name="FilterList" Margin="10,0" Grid.Row="2"
Grid.ColumnSpan="3" Background="White" ItemTemplateSelector="{StaticResource
ReportFilterTemplateSelector}" ItemsSource="{Binding reportParameters,
Mode=TwoWay}" ScrollViewer.CanContentScroll="False">
One of my sample datatemplates can be seen below. Everything shows up great. My problem, is that for this (and other) datatemplates, I can have multiple instances of the same one. In this particular instance, the treeview itemssource is bound to DataContext.OfficeListText to populate all the elements.
<DataTemplate x:Key="office">
<Grid MinHeight="35" MaxHeight="250">
<Grid.RowDefinitions>
<RowDefinition/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="100"/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<TextBlock Text="{Binding rpName}" VerticalAlignment="Center" Grid.Row="0" Grid.Column="0" />
<Expander HorizontalAlignment="Stretch" Grid.Row="0" Grid.Column="1"
Header="{Binding Path=DataContext.OfficeListText, RelativeSource=
{RelativeSource FindAncestor, AncestorType={x:Type UserControl}}}"
VerticalAlignment="Top" ExpandDirection="Down">
<TreeView Tag="{Binding rpParameter}" HorizontalAlignment="Stretch"
VerticalAlignment="Stretch" ItemsSource="{Binding
Path=DataContext.OfficeList, RelativeSource={RelativeSource
FindAncestor, AncestorType={x:Type UserControl}}, Mode=TwoWay}"
ItemTemplate="{StaticResource CheckBoxItemTemplate}"
ItemContainerStyle="{StaticResource TreeViewItemStyle}"/>
</Expander>
</Grid>
</DataTemplate>
The main problem with this is one, for instance, if I select an office in say the first treeview, the 2nd treeview shows the same. Essentially I want them to have the same itemssource initially but have separate instances. Since they are generated dynamically that is where I'm getting stuck. Any help would be appreciated.
I'm not sure what other code would be necessary, since I'm sure most of it will be irreverent based on what I'll need to do to make this work, but if you would like more I will gladly provide. Thanks!

Currently your TreeView binds to a single instance of OfficeList that belongs to your UserControl's DataContext. This means that every TreeView points to the same list. If I understand your question correctly, what you really want is to have a different instance of OfficeList for each TreeView.
I don't recommend instantiating a new OfficeList each time the DataTemplate is applied to a reportParameter. You could do that with a ValueConverter, but it would be pretty hacky.
The cleaner solution would be to have a class that contains the data for your reportParameter, plus an instance of OfficeList, and then bind to instances of that class instead of binding to the UserControl. Depending on how the reportParameters are structured (I am going to assume that reportParameters is a list of objects of type ReportParameter), there are two ways you might want to do this:
1) If ReportParameter is a ViewModel, you can simply add an OfficeList property to the ReportParameter class, and initialize it when you instantiate each ReportParameter.
Then your TreeView would look like this:
<TreeView Tag="{Binding rpParameter}" HorizontalAlignment="Stretch"
VerticalAlignment="Stretch" ItemsSource="{Binding Path=OfficeList, Mode=TwoWay}"
ItemTemplate="{StaticResource CheckBoxItemTemplate}"
ItemContainerStyle="{StaticResource TreeViewItemStyle}"/>
2) If ReportParameter is not a ViewModel (and thus it would not be appropriate to add an OfficeList parameter), then create a new class called something like ReportParameterViewModel that contains 2 properties: ReportParameter and OfficeList. Instead of binding your ListView to a list of ReportParameter, bind to a list of ReportParameterViewModel.
Then your ListView will look something like this:
<ListView Panel.ZIndex="0" x:Name="FilterList" Margin="10,0" Grid.Row="2"
Grid.ColumnSpan="3" Background="White" ItemTemplateSelector="{StaticResource
ReportFilterTemplateSelector}" ItemsSource="{Binding reportParameterViewModels,
Mode=TwoWay}" ScrollViewer.CanContentScroll="False">
Your TextBlock will look like this:
<TextBlock Text="{Binding ReportParameter.rpName}" VerticalAlignment="Center" Grid.Row="0" Grid.Column="0" />
Your TreeView will look like this:
<TreeView Tag="{Binding rpParameter}" HorizontalAlignment="Stretch"
VerticalAlignment="Stretch" ItemsSource="{Binding Path=OfficeList, Mode=TwoWay}"
ItemTemplate="{StaticResource CheckBoxItemTemplate}"
ItemContainerStyle="{StaticResource TreeViewItemStyle}"/>

Related

Binding to viewmodel from inside a datatemplate

I have multiple videos displayed they are bound with a videocollection in Mainviewmodel. Everything works fine untill I try to bind the enter command to Mainviewmodel. I Don't know the syntax for this. As it stands the binding is set to Video and not Mainviewmodel.
Errormessage:
'StartVideoCommand' property not found on 'object' ''Video'
Xaml:
<Window.Resources>
<local:MainViewModel x:Key="MainViewModel"/>
</Window.Resources>
<Grid DataContext="{StaticResource MainViewModel}">
<ListBox ItemsSource="{Binding Videos}">
<ListBox.ItemTemplate>
<DataTemplate>
<Grid>
<Grid.InputBindings>
!!! <KeyBinding Key="Enter" Command="{Binding StartVideo}" /> !Bound to Video not to Mainviewmodel grrr
</Grid.InputBindings>
... layout stuff
<TextBlock Text="{Binding Title}" Grid.Column="0" Grid.Row="0" Foreground="White"/>
<TextBlock Text="{Binding Date}" Grid.Column="0" Grid.Row="1" Foreground="White" HorizontalAlignment="Left"/>
<TextBlock Text="{Binding Length}" Grid.Column="1" Grid.Row="1" Foreground="White" HorizontalAlignment="Right"/>
... closing tags
Command="{Binding RelativeSource={RelativeSource AncestorType={x:Type Window}}, Path=DataContext.StartVideo}"
Another approach would be to use ElementName binding instead of RelativeSource.
Example:
<Window x:Name="root" ... >
...
Command="{Binding ElementName=root, Path=DataContext.StartVideo}"
...
A possible advantage over RelativeSource is that this is explicit; if someone changes the XAML hierarchy then relative references could break unintentionally. (Not likely in this specific example of binding to a Window however).
Also if your "root" element already is named then so much the better it is easy to take advantage of.
It is also somewhat more readable.

Using Wpf RelativeSource Binding breaks Blendability

I've got the following scenario... I have a Window that contains an ItemsControl. I specify a ViewModel for the Window's DataContext. I specify a DataTemplate for the ItemControl's ItemTemplate. In the the DataTemplate I use a ComboBox and for the ComboBox's ItemsSource I use a RelativeSource Binding to its containing Window's DataContext. During Run-Time everything works fine and the Binding is resolved correctly, but during Design-Time Cider can't pick up the containing Window's ViewModel to which the ItemSource is binding.
Here is my code (I left out the xml namespace declarations at the top, but in my code they are included):
<Window d:DataContext="{Binding Source={StaticResource DesignViewModel}}">
<Window.Resources>
<designviewmodels:GenresEditorDesignViewModel x:Key="DesignViewModel" />
</Window.Resources>
<ItemsControl Grid.Row="0" Margin="3" ItemsSource="{Binding Path=CurrentState}" >
<ItemsControl.ItemTemplate>
<DataTemplate>
<Grid DataContext="{Binding}">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"></ColumnDefinition>
<ColumnDefinition Width="20"></ColumnDefinition>
</Grid.ColumnDefinitions>
<ComboBox Grid.Column="0" Margin="3,0,3,0"
ItemsSource="{Binding RelativeSource={RelativeSource Mode=FindAncestor,
AncestorType={x:Type Window}}, Path=DataContext.AvailableGenres,
Mode=OneWay}"
DisplayMemberPath="Name"
SelectedItem="{Binding Path=Genre, Mode=TwoWay}" DataContext="
{Binding}" />
</Grid>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Window>
So basically from the above piece of code the Path=DataContext.AvailableGenres can not be resolved during Design Time, but during Run Time it is resolved correctly.
Does anybody know if i'm doing something wrong or is it a problem with the Wpf xaml parser that it cannot resolve bindings to RelativeSources during design time?
I know this is an old question, but for the sake of posterity I have a solution that works for me.
I have never been able to get RelativeSource bindings to be Blendable. However, you can provide sign-posts to the design-time environment(s) if you are fortunate enough to have an ancestor without a binding set against it.
On the spare ancestor (in this case, the Grid) bind the DataContext to the same RelativeSource except with the Path set to just the DataContext. Then, apply a d:DataContext to the same ancestor, providing it with the type (or equivalent mock) you want to bind to on your actual, original element. Finally, bind your original element (the ComboBox) to the property or path as per normal.
<Grid
DataContext="{Binding RelativeSource={RelativeSource Mode=FindAncestor,
AncestorType={x:Type Window}}, Path=DataContext,
Mode=OneWay}"
d:DataContext="{Binding Source={StaticResource DesignViewModel}}" >
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"></ColumnDefinition>
<ColumnDefinition Width="20"></ColumnDefinition>
</Grid.ColumnDefinitions>
<ComboBox Grid.Column="0" Margin="3,0,3,0"
ItemsSource="{Binding Path=AvailableGenres, Mode=OneWay}"
DisplayMemberPath="Name"
SelectedItem="{Binding Path=Genre, Mode=TwoWay}" DataContext="
{Binding}" />
</Grid>

WPF Nested Databinding; How Do I Bind To An Item Inside Another Item

I have a ListView bound to an ObservableCollection of CustomerContacts.
It works great so far, but being new to this, I'm not sure how to do the next part.
Each customer contact has several contact types, which I want to display under their name.
So inside of CustomerContacts I have another ObservableCollection of ContactTypes.
Here is my current datatemplate:
<DataTemplate x:Key="iconTemplate">
<DockPanel Height="133" Width="150">
<Image Source="/Tracks%203.5;component/Images/NoPic.png" Height="25" Width="25" Margin="1,0" />
<TextBlock DockPanel.Dock="Top" Text="{Binding FullName}" Margin="5,3,5,0" FontWeight="Bold" HorizontalAlignment="Left" />
<<TextBlock Text="{Binding Title}" Margin="5,0,5,3" HorizontalAlignment="Left" />>
</DockPanel>
</DataTemplate>
And here's my first attempt at putting the listview inside:
<DataTemplate x:Key="iconTemplate">
<DockPanel Height="133" Width="150">
<Image Source="/Tracks%203.5;component/Images/NoPic.png" Height="25" Width="25" Margin="1,0" />
<TextBlock DockPanel.Dock="Top" Text="{Binding FullName}" Margin="5,3,5,0" FontWeight="Bold" HorizontalAlignment="Left" />
<ListView ItemsSource="{Binding ContactTypes}">
<ListView.Template>
<ControlTemplate TargetType="ItemsControl">
<ItemsPresenter/>
</ControlTemplate>
</ListView.Template>
<ListView.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Horizontal"/>
</ItemsPanelTemplate>
</ListView.ItemsPanel>
<ListView.ItemTemplate>
<DataTemplate>
<TextBlock Margin="3,0,0,0" HorizontalAlignment="Center" Text="{Binding Path=ContactType}"/>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
<!--<TextBlock Text="{Binding Title}" Margin="5,0,5,3" HorizontalAlignment="Left" />-->
</DockPanel>
</DataTemplate>
I want to replace the TextBlock bound to Title with a ListView/ListBox/ItemsControl bound to the items in ContactTypes.
Somewhat similar to this question: < WPF: bind to a List inside a class > but without all the codebehind. It would be nice to have it in the XAML.
There's a couple things I would do here:
Simplify what you're trying to do in order to isolate the problem. Instead of that whole ListView business, just put in there an ItemsControl: <ItemsControl ItemsSource="{Binding ContactTypes}" /> That'll give you the default view of things, which may just show you the data type of your objects (whatever ContactTypes is), but it'll let you know if the binding is working. If it is, you'll get something listed there. If it isn't, you won't.
If you're not getting anything listed, go use Snoop to drill into it and look for errors. Snoop shows the databinding errors, allows you to inspect the DataContext of each of the items, etc.
If you're still having problems, it might benefit us if you posted your class definitions and your code where you wire things up. There might be some other underlying issues (for instance, is the ContactTypes property null when the binding initially occurs and you're not using INPC to let the binding system know when it changes?).

I can't seem to get a DataTemplate to work in WPF

I have tried several different ways to get a simple DataTemplate example to work. But, I am not having any luck. The data context for the XAML below is being set in the code-behind. The two code examples included here are wrapped in the element in my application but, that is the only outside consideration. The first code example works. It displays the data. But, if I put the functionality in a DataTemplate, and then try to use the template, it does not work.
Working Example:
<Canvas Height="100" Width="300">
<TextBlock Text="{Binding Path=DataSheet.Item.ClassId}" Canvas.Left="10"></TextBlock>
<TextBlock Text="{Binding Path=DataSheet.Item.ClassName}" Canvas.Right="100"></TextBlock>
</Canvas>
Example that DOES NOT work (but, no error is thrown):
<Window.Resources>
<DataTemplate x:Key="FirstTemplate">
<Grid Margin="4">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" SharedSizeGroup="Key" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<TextBox Text="{Binding ClassId}"></TextBox>
<TextBox Text="{Binding ClassName}"></TextBox>
</Grid>
</DataTemplate>
</Window.Resources>
<Grid>
<ListBox ItemsSource="{Binding Path=DataSheet.Item}" Grid.IsSharedSizeScope="True"
HorizontalAlignment="Stretch"
ItemTemplate="{StaticResource ResourceKey=FirstTemplate}"/>
</Grid>
Any advice as to what I am doing wrong would really be appreciated.
Thanks.
Your ItemSource should be a collection meanwhile DataSheet.Item looks like a single item.
You should wrap it into collection.
Or you could manually add ListBoxItem.
<ListBox>
<ListBoxItem Content="{Binding DataSheet.Item}" ContentTemplate="{StaticResource FirstTemplate}"/>
</ListBox>
From the working code you have presented, I am assuming that DataSheet.Item is not IEnumerable.
If it is not IEnumerable binding it to ListBox.ItemsSource doesn't seem appropriate.
Try this:
<ListBox ItemsSource="{Binding Path=DataSheet.Item}" Grid.IsSharedSizeScope="True"
HorizontalAlignment="Stretch"
ItemTemplate="{StaticResource FirstTemplate}"/>
Here's a link for more info

Do i have to build a ControlTemplate? Or is there an alternative?

I got a TreeView and want to display the Data nested (not hierarchically). The first level data is called TaskViewModel, the second level data is ArtifactViewModel. I want the ArtifactViewModel horizontal inside a GroupBox which represents TaskViewModel.
I tried different approaches, this is my last one:
<TreeView Name="tvTasks" ItemsSource="{Binding Tasks}">
<TreeView.Resources>
<HierarchicalDataTemplate DataType="{x:Type vm:TaskViewModel}">
<GroupBox Header="{Binding Name, UpdateSourceTrigger=PropertyChanged}">
<StackPanel Orientation="Vertical">
<ListView ItemsSource="{Binding Children}"/>
<TextBlock Text="{Binding Description, UpdateSourceTrigger=PropertyChanged}" TextWrapping="Wrap"/>
</StackPanel>
</GroupBox>
</HierarchicalDataTemplate>
<DataTemplate DataType="{x:Type vm:ArtifactViewModel}">
<Border Background="{Binding Type,Converter={StaticResource Type2Background}}"
Margin="5" BorderBrush="Black" BorderThickness="2" CornerRadius="2">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="80"/>
<RowDefinition Height="20"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="100"/>
</Grid.ColumnDefinitions>
<TextBlock Text="{Binding Name, UpdateSourceTrigger=PropertyChanged}"
TextAlignment="Center" Background="Black" Foreground="White"
Opacity="0.75" Grid.Column="0" Grid.Row="1"/>
</Grid>
</Border>
</DataTemplate>
</TreeView.Resources>
</TreeView>
This looks pretty much like what i want, besides that ArtifactViewModels are shown vertical. And if i click on ArtifactViewModel the tvTasks.SelectedItem doesn't change, because the ListView handels this event. I know that this approach is not the cleverest, but it's just a try.
I looked at this article, but i don't see how to deal with the different objects i want to put in the TreeView. So ... how do i build such a UI?
The main issue you're running into here is that you're nesting multiple controls, each with their own selected items.
If you're planning on showing the data as nested but not hierarchically, don't bother using TreeView. If you want to have one item be selectable at any given point in time, use a ListBox instead.
Now the tricky part is playing around with how you want to lay the items out. Take a look at Bea Stollnitz's example here where she redraws a ListBox as a Canvas. You could do something similar where the ItemsPanelTemplate is a Canvas, and you calculate the x,y coordinates. Alternately, you could use a Grid, and determine the Grid.Row and Grid.Column values.

Resources