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

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.

Related

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

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}"/>

Stretching the items of an ItemsControl

I'm bulding a small WPF app over here. It's all built strictly with MVVM, using nothing but DataTemplates linked to view model types.
I've seen alot of questions about how to stretch and clip the contents of ListBoxes so that the items fill its parent. After alot of experimenting I managed to get my head around that but now I find myself in the same scenario with the ItemsControl but the same tricks doesn't seem to work.
Here's one of the DataTemplates in use (a simple TextBox). Note how I tried setting the HorizontalAlignment ...
<DataTemplate DataType="{x:Type vm:OneOfMyViewModelTypes}">
<TextBox
Text="{Binding Path=Value}"
HorizontalAlignment="Stretch"
/>
</DataTemplate>
Here's the ItemsControl inside a Grid ...
<Grid Background="Gray">
<Grid.Margin>
<Thickness
Left="{StaticResource ConfigurationDefaultMargin}"
Right="{StaticResource ConfigurationDefaultMargin}"
Bottom="{StaticResource ConfigurationDefaultMargin}"
Top="{StaticResource ConfigurationDefaultMargin}"
/>
</Grid.Margin>
<Grid.ColumnDefinitions>
<ColumnDefinition SharedSizeGroup="_key" Width="Auto"/>
<ColumnDefinition SharedSizeGroup="_value" Width="*"/>
</Grid.ColumnDefinitions>
<ItemsControl
Background="DimGray"
Grid.IsSharedSizeScope="True"
ItemsSource="{Binding Path=Configuration, Mode=OneWay}"
HorizontalAlignment="Stretch"
HorizontalContentAlignment="Stretch"
>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition SharedSizeGroup="_key"/>
<ColumnDefinition SharedSizeGroup="_value"/>
</Grid.ColumnDefinitions>
<TextBlock
Style="{StaticResource ExtensionConfigurationLabel}"
Grid.Column="0"
Margin="5,5,5,0"
Text="{Binding Path=Caption}"
/>
<ContentPresenter
Grid.Column="1"
HorizontalAlignment="Stretch"
Margin="5,5,5,0"
Content="{Binding}"
/>
</Grid>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Grid>
I've used colors to see how the controls sizes. The Grid is gray and the ItemsControl is dark gray.
This is the result ...
As you can see from the colors the containing Grid stretches while the ItemsControl does not. I did set its HorizontalAlignment property to Stretch but it seems it has no effect. Is there anything else I need do?
Thanks
You have two columns in your main (outer) grid, yet you use only the first column.
The second column uses all the remaining space.

How to create a ListBox that looks like the Windows Live Tiles?

I want to create a ListBox that looks like the Windows Live Tiles.
This is my XAML:
<ListBox Name="listBox"
Grid.Row="1"
Background="Transparent"
BorderBrush="Transparent"
BorderThickness="0"
Width="Auto" Height="Auto"
ScrollViewer.HorizontalScrollBarVisibility="Disabled"
ItemsSource="{Binding Path=SomeList,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}"
VerticalAlignment="Stretch" HorizontalAlignment="Stretch"
VerticalContentAlignment="Stretch" HorizontalContentAlignment="Stretch">
<ListBox.ItemTemplate>
<DataTemplate >
<Grid x:Name="PART_Item" Width="Auto" Height="Auto" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Margin="10">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Image Name="logo" Source="{Binding Path=ImagePath}"
Width="50" Height="50"
Margin="0,0,10,0"
Grid.Column="0" Grid.Row="0" Grid.RowSpan="2"
RenderTransformOrigin="0.5,0.5"
SnapsToDevicePixels="True">
</Image>
<TextBlock Grid.Row="0" Grid.Column="1"
TextAlignment="Left"
FontSize="12"
Text="{Binding Path=SomeName}"
TextTrimming="CharacterEllipsis"/>
<TextBlock Grid.Row="1" Grid.Column="1"
Margin="0,10,0,0"
FontSize="12"
TextAlignment="Left"
Text="{Binding Path=SomeNo}"
TextTrimming="CharacterEllipsis"/>
<TextBlock Grid.Row="0" Grid.Column="2"
TextAlignment="Left"
FontSize="12"
Text="{Binding Path=SomeDescription}"
TextWrapping="Wrap"/>
</Grid>
</DataTemplate>
</ListBox.ItemTemplate>
How do i change this XAML to look like Windows Live Tiles?
2 Items next to each other for 2 Rows and then 1 Item with a ColumnSpan of 2 and so on it should continue.
I only want to to be in the above mentioned layout.
How would i achieve this?
The image is just for a reference. I want may layout to be like the first 3 Rows.
One easy solution is to use a WrapPanel as the ListBox ItemsPanel. Make your items have the correct dimension (square or rectangle) using DataBinding and give the ListBox the width of two squares.
I'd probably do it with a WrapPanel. Give the ListBox an ItemsPanelTemplate with a WrapPanel in it:
<ListBox.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel Orientation="Horizontal" />
</ItemsPanel>
</ListBox>
Then set your ItemTemplate up so that the size of each item is half the width of the ListBox itself (or the same width, for your example third row). The easiest way to do that is to set the ListBox to a known width, then bind the width of the top-level panel in the DataTemplate to something in the item objects. However, this means the objects need to know how wide they're supposed to be - but if they want to fill the tile sizes properly, maybe that could be seen as a benefit.
Another option is a DataTemplateSelector, but that also needs to have a way of determining if it should deliver a wide or narrow template and it doesn't really have access to information about where in the list it is - it just knows which item to deliver a template for.
If you really want to do it without the items having to know anything about where in the list they're going to appear, I think you're going to have to step back from ListBox and write more of the logic yourself. You might be able to wrangle an ItemsControl into behaving like this, but I think it's more likely you'd need to go back further still and do a full-blown custom control, depending on how much flexibility you actually want.
But judging by your question, I think you'll probably be okay just retemplating the ListBox.

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

wpf border control to span the width of listboxItem

Im trying to define a dataTemplate for a business object in my wpf application a collection of which is being bound to a ListBox.
<UserControl.Resources>
<DataTemplate x:Key="ResizedItemsDataTemplate" DataType="{x:Type resizer:ResizeMonitorItem}">
<Border x:Name="bdr" BorderBrush="Blue"
BorderThickness="1"
CornerRadius="2"
Width="auto"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch">
<Grid Margin="2">
<Grid.RowDefinitions>
<RowDefinition Height="14"></RowDefinition>
<RowDefinition Height="14"></RowDefinition>
</Grid.RowDefinitions>
<TextBlock Grid.Row="0" Text="{Binding SaveAsFileName}"></TextBlock>
<TextBlock Grid.Row="1" Text="{Binding ResizedImageFilePath}"></TextBlock>
</Grid>
</Border>
</DataTemplate>
</UserControl.Resources>
<Grid VerticalAlignment="Stretch" HorizontalAlignment="Stretch" Margin="0">
<Border BorderThickness="0,0,0,5" BorderBrush="DarkGray" >
<ListBox x:Name="ListBoxResizeItems" ItemsSource="{Binding Path=ResizeItems}" BorderThickness="0" ItemTemplate="{DynamicResource ResizedItemsDataTemplate}">
</ListBox>
</Border>
</Grid>
How can I get the border defined with x:Name=bdr to span the full width of each listbox item? At the moment it only spans the with of the textblocks inside it which dont neccessary fill the full width of the listboxitem and also vary for each listboxitem.
This is probably more to do with the ListBoxItems themselves not taking up the full width of the ListBox. Add the HorizontalContentAlignment="Stretch" attribute to your ListBox and see if it stretches the individual items to fill the width.
Worked it out. The trick is to set the HorizontalContentAlignment="Stretch" on your listbox to make its contents stretch the full width rather than fit the contents only.
<ListBox x:Name="ListBoxResizeItems"
HorizontalContentAlignment="Stretch"
ItemsSource="{Binding Path=ResizeItems}"
BorderThickness="0"
ItemTemplate="{DynamicResource ResizedItemsDataTemplate}" >
</ListBox>
Sorry Matt, just got your answer thorugh as I was typing this post.
HorizontalContentAlignment is a nice, clean solution compared to what I was trying. Thanks!
Here's what ALMOST worked, but sometimes made a dialog box animated itself wider and wider forever:
Width="{Binding ActualWidth,
RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type ListBox}}}"

Resources