I am trying to create a simple 2 level Treeview in WPF (MVVM approach). For my first level I have a standard datatemplate, for my second level I want to use a Template Selector so that I can change the appearance of each item based on one of its properties.
Below is my Treeview xaml
<Treeview ItemsSource={Binding ListA}>
<TreeView.ItemTemplate>
<HierarchicalDataTemplate ItemsSource="{Binding ListB}" ItemTemplateSelector={StaticResource TemplateSelector}>
<Textblock Text={Binding Name}/>
</HierarchicalDataTemplate>
</TreeView.ItemTemplate>
</TreeView>
My first level is
<Textblock Text={Binding Name}/>
will just display a name
For my second level the TemplateSelector is returning a datatemplate which is something like
<DataTemplate x:Key="SomeKey">
<StackPanel Orientation="Horizontal">
<ViewBox>
-----
</ViewBox>
<TextBlock Text={Binding Name}/>
</StackPanel>
</DataTemplate>
But all I see for my second level is my second level ViewModel name. I double checked the template selector and it is definitely returning the correct data template but it is just not getting displayed.
Can anyone please point me in the right direction?
Edit -- Added more code as per request
this is my template selector
public class DataFieldsDataTemplateSelector : DataTemplateSelector
{
public DataTemplate AlphaTemplate { get; set; }
public ------
public ------
public DataFieldsDataTemplateSelector()
{
//This is getting the template from my ResourceDictionary
AlphaTemplate = (DataTemplate)dDictionary["alphaTemplate"];
}
public override DataTemplate SelectTemplate(object item,DependencyObject container)
{
//Somecode
return AlphaTemplate;
}
}
my template for AlphaTemplate in my dictionary is
<DataTemplate x:Key="alphaTemplate">
<Grid >
<Grid.ColumnDefinitions>
<ColumnDefinition Width="15"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Viewbox IsHitTestVisible="False">
<Path Data="M0,0L56.622002,0 56.622002,14.471 35.715,14.471 35.715,64 20.715,64 20.715,14.471 0,14.471z" Stretch="Uniform" Fill="{DynamicResource ButtonForegroundNormal}" VerticalAlignment="Center" Width="15" Height="15" Margin="0,0,0,0" RenderTransformOrigin="0.5,0.5">
<Path.RenderTransform>
<TransformGroup>
<TransformGroup.Children>
<RotateTransform Angle="0" />
<ScaleTransform ScaleX="1" ScaleY="1" />
</TransformGroup.Children>
</TransformGroup>
</Path.RenderTransform>
</Path>
</Viewbox>
<textBlock Text="{Binding Name}/>
</Grid>
</DataTemplate>
my class TypeB contains a Name(Text) and DataType(Text) Fields
if the DataType is Alpha I return AlphaTemplate in my templateSelector and so on
I have an action(dragDrop) on the window which adds items to the second level. And I want the template selector should pick up the correct datatemplate for that dropped item based on its DataType
My main ViewModel contains ICollectionView of TypeA Objects and Each TypeA ViewModel contains ICollectionView of TypeB ViewModels.
Let me know if you need anything
I dont know what is wrong with this as this will require to debug the code, but what you wanted to achieve can be done by defining the default DataTemplate for your TypeB and switching the content depending on the binding like this:
<DataTemplate DataType="{x:Type TypeB}">
<ContentControl>
<ContentControl.Style>
<Style TargetType="{x:Type ContentControl}">
<Setter Property="ContentTemplate">
<Setter.Value>
<!-- Default template here for your item -->
</Setter.Value>
</Setter>
<Style.Triggers>
<DataTrigger Binding="{Binding XYZ}" Value="true">
<Setter Property="ContentTemplate">
<Setter.Value>
<!-- Different template for your item -->
</Setter.Value>
</Setter>
</DataTrigger>
</Style.Triggers>
</Style>
</ContentControl.Style>
</ContentControl>
</DataTemplate>
Thanks
Related
I'm currently trying to write proper style for item displayed as selected in combobox. The reason I'm doing it is, that I have not much control over how ComboBox display the selected item and - for example - on dark background, the item is still displayed black.
I came with the following solution:
<DataTemplate x:Key="MyItem" DataType="ComboBoxItem">
<TextBlock Text="{Binding}" Foreground="White"/>
</DataTemplate>
<!-- (...) -->
<Style TargetType="{x:Type ComboBox}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate>
<!-- ... -->
<!-- Displaying currently selected item -->
<ContentPresenter Margin="2" IsHitTestVisible="False"
VerticalAlignment="Center" HorizontalAlignment="Stretch"
Name="ContentSite"
ContentTemplate="{StaticResource MyItem}"
Content="{TemplateBinding ComboBox.SelectionBoxItem}" />
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
Now when the simple ComboBoxItem is selected, it is correctly displayed in the ComboBox. On the other hand, if I - for example - display a button with some content, in return I get the text System.Windows.Shapes.Rectangle, what is far from what I want to display.
I would like to use different templates for different data types displayed in the ComboBox - I will be able to customize their appearance. How can I achieve that?
Edit:
To be perfectly clear, I'm talking about selected (=chosen) ComboBox item in this context:
(not about the selected ComboBox item on the ComboBox's list)
It seems to me that you are looking for ContentTemplateSelector:
http://msdn.microsoft.com/en-us/library/system.windows.controls.contentcontrol.contenttemplateselector%28v=vs.110%29.aspx
http://zamjad.wordpress.com/2011/09/21/using-contenttemplateselector/
Take a look at those links.
Right, there are a few different aspects to this question. Firstly, we can get rid of the default selection colours by adding this into the Resources section:
<SolidColorBrush x:Key="{x:Static SystemColors.HighlightBrushKey}" Color="Transparent" />
<SolidColorBrush x:Key="{x:Static SystemColors.ControlBrushKey}" Color="Transparent" />
<SolidColorBrush x:Key="{x:Static SystemColors.HighlightTextBrushKey}" Color="White" />
<SolidColorBrush x:Key="{x:Static SystemColors.ControlTextBrushKey}" Color="Black" />
Experiment with these settings as two of them define the colour of the text and two set the colour for the background of the selected item. In fact, you can use whatever colours here to provide a quick way to change the selected item colours... without having to go to all of the trouble that you have gone to. But for your example, let's just leave them as Transparent.
Ok, so next you want to have a different look for each different data type that appears in the ComboBox... this can also be easily achieved. All you need to do is to declare a DataTemplate for each type and do not set the x:Key property so that they will be implicitly applied. Here is a basic example:
<Window.Resources>
<DataTemplate DataType="{x:Type Type1}">
<TextBlock Text="{Binding}" Foreground="Red" />
</DataTemplate>
<DataTemplate DataType="{x:Type Type2}">
<TextBlock Text="{Binding}" Foreground="Green" />
</DataTemplate>
<DataTemplate DataType="{x:Type Type3}">
<TextBlock Text="{Binding}" Foreground="Blue" />
</DataTemplate>
</Window.Resources>
If you populated a ComboBox with items of these three types with these DataTemplates, then the items would be coloured in Red, GreenandBlue`.
I'll leave the solution with ContentTemplateSelector for all, who will seek solution for the same problem.
Add the following class to your library/application:
public class ComboBoxSelectionTemplateSelector : DataTemplateSelector
{
public DataTemplate TextTemplate
{
get;
set;
}
public DataTemplate ContentTemplate
{
get;
set;
}
public override System.Windows.DataTemplate SelectTemplate(object item, DependencyObject container)
{
if (item is string)
return TextTemplate;
else
return ContentTemplate;
}
}
Define two different DataTemplates:
<DataTemplate x:Key="ComboBoxTextItem">
<TextBlock Text="{Binding}" Foreground="{DynamicResource {x:Static vs:VsBrushes.WindowTextKey}}" />
</DataTemplate>
<DataTemplate x:Key="ComboBoxOtherItem">
<ContentPresenter Content="{Binding}" />
</DataTemplate>
Instantiate the selector inside style you want to use it in:
<Style.Resources>
<local:ComboBoxSelectionTemplateSelector ContentTemplate="{StaticResource ComboBoxOtherItem}"
TextTemplate="{StaticResource ComboBoxTextItem}" x:Key="ContentTemplateSelector" />
</Style.Resources>
Use it in the ContentPresenter:
<!-- ... -->
<ControlTemplate TargetType="ComboBox">
<Grid>
<ContentPresenter ContentTemplateSelector="{StaticResource ContentTemplateSelector}"
Content="{TemplateBinding ComboBox.SelectionBoxItem}" />
<!-- Other items -->
<Popup ... /> <!-- For displaying ComboBox's list -->
</Grid>
I have ComboBox with custom ItemTemplate.
<ComboBox Height="20" Width="200"
SelectedItem="{Binding Path=SelectedDesign}"
ItemsSource="{Binding Path=Designs}" HorizontalAlignment="Left"
ScrollViewer.CanContentScroll="False">
<ComboBox.ItemTemplate>
<DataTemplate DataType="{x:Type formdesign:FormDesignContainer}">
<Rectangle Width="200" Height="100">
<Rectangle.Fill>
<ImageBrush ImageSource="{Binding Path=ImageThumb}" Stretch="Uniform" />
</Rectangle.Fill>
</Rectangle>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
This works well. However WPF tries to draw rectangle as Combobox Text. How can I set "text" for this template. By "text" I mean string or control which represent selected item and write into combobox when item is selected
In other words I'd like to do this:
But now I got this
Try setting SelectionBoxItemTemplate with a TextBlock.
Appears that SelectionBoxItemTemplate is read-only. So another approach is to override ItemContainerStyle.Template. Example
I found this solution by Ray Burns a good approach. You can define two DataTemplate one for Items in the drop down list and the other for the selected item which should be shown in the Combobox. The using a trigger and checking the visual tree it decide which one to use.
<Window.Resources>
<DataTemplate x:Key="NormalItemTemplate" ...>
...
</DataTemplate>
<DataTemplate x:Key="SelectionBoxTemplate" ...>
...
</DataTemplate>
<DataTemplate x:Key="CombinedTemplate">
<ContentPresenter x:Name="Presenter"
Content="{Binding}"
ContentTemplate="{StaticResource NormalItemTemplate}" />
<DataTemplate.Triggers>
<DataTrigger
Binding="{Binding RelativeSource={RelativeSource FindAncestor,ComboBoxItem,1}}"
Value="{x:Null}">
<Setter TargetName="Presenter" Property="ContentTemplate"
Value="{StaticResource SelectionBoxTemplate}" />
</DataTrigger>
</DataTemplate.Triggers>
</DataTemplate>
</Window.Resources>
...
<ComboBox
ItemTemplate="{StaticResource CombinedTemplate}"
ItemsSource="..."/>
Add Textblock to the datatemplate and bind it
or add Contentpersenter on the rectangle
Edit:
it seems like i didn't got what you were tring to accomplish ,
I have a ObservableCollection> property that I would like to display in a list box in a WPF window using the MVVM model. Ideally I would like to have a listbox that has a check box for the bool and a label for the string and when the user changes the value of the checkbox it would change the corresponding bool. What is the best way to go about doing this?
Try using an ItemsControl, and don't bind to a list of checkboxes, define a model:
<ItemsControl ItemsSource="{Binding YourModelList}">
<ItemsPanelTemplate>
<StackPanel Orientation="Horizontal">
<CheckBox IsChecked="{Binding Path=Selected}"/>
<TextBlock Text="{Binding Path=Description}" />
</StackPanel>
</ItemsPanelTemplate>
</ItemsControl>
In the ViewModel:
public ObservbleCollection<YourModel> YourModelList { get; set; }
And the Model:
public class YourModel
{
public bool Selected {get;set;}
public string Description {get;set;}
public int ID {get;set;}
}
Implement INotifyPropertyChanged as necessary
Idealy, if you want to follow the MVVM approach, everyone of your elements in the ObservableCollection should be a ViewModel.
These viewModels should expose 2 properties, such as string Description and bool IsSelected.
All you need then is to provider your ListBox with a Style in order to display a checkbox and textblock for each databound ViewModel.
The following XAML implements such a Style. Note: The usercontrol DataContext should hold a ViewModel containing an ObservableCollection<YourClass> Items { get; set; } property where YourClass exposes string Description { get; set; } and bool IsSelected { get; set; }. You will obviously want to throw in some INotifyPropertyChanged magic in there.
<Grid>
<Grid.Resources>
<Style x:Key="CheckBoxListStyle" TargetType="ListBox">
<Style.Resources>
<Style TargetType="ListBoxItem">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="ListBoxItem">
<StackPanel Orientation="Horizontal">
<CheckBox x:Name="itemChoix" Margin="5,5,0,0" IsChecked="{Binding IsSelected, Mode=TwoWay}" IsEnabled="{Binding IsEnabled, Mode=TwoWay}" />
<TextBlock Margin="5,5,0,0" Text="{Binding Description, Mode=TwoWay}" />
</StackPanel>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</Style.Resources>
<Setter Property="ItemsPanel">
<Setter.Value>
<ItemsPanelTemplate>
<WrapPanel Orientation="Vertical" />
</ItemsPanelTemplate>
</Setter.Value>
</Setter>
<Setter Property="BorderThickness" Value="0" />
</Style>
</Grid.Resources>
<Grid.ColumnDefinitions>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="25"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<Border Grid.Row="1" Style="{StaticResource BoxBorder}" Grid.Column="1" VerticalAlignment="Stretch" HorizontalAlignment="Stretch">
<Grid>
<ListBox ItemsSource="{Binding Path=Items}" SelectionMode="Multiple" Style="{StaticResource CheckBoxListStyle}"/>
</Grid>
</Border>
</Grid>
Create your public ObservableCollection property in your ViewModel
Create an IsSelected bool property.
Populate collection from constructor or command.
Create a command for the checkbox that sets the IsSelected property.
Do the Xaml binding
I am trying to do something that should be brain-dead simple, however, I cannot get it to work. I am displaying a list of items in a listbox. I have added check boxes to the list box so that the user can select multiple items. However, even though the object in the list being bound to the ListBox has an "IsSelected" property, it is not being bound. I could use some help as this is driving me nuts.
<Style x:Key="CheckBoxListStyle" TargetType="{x:Type ListBox}">
<Setter Property="SelectionMode" Value="Multiple"></Setter>
<Setter Property="ItemContainerStyle">
<Setter.Value>
<Style TargetType="{x:Type ListBoxItem}">
<Setter Property="Margin" Value="2"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type ListBoxItem}">
<CheckBox Focusable="False"
IsChecked="{Binding Path=IsSelected, RelativeSource={RelativeSource TemplatedParent}, Mode=TwoWay}">
<ContentPresenter></ContentPresenter>
</CheckBox>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</Setter.Value>
</Setter>
<ListBox
Style="{StaticResource CheckBoxListStyle}"
IsEnabled="{Binding Path=SpecificClients.Value, Mode=OneWay}"
ItemsSource="{Binding Path=SelectedClients}"
VirtualizingStackPanel.IsVirtualizing="True"
VirtualizingStackPanel.VirtualizationMode="Recycling"
ScrollViewer.VerticalScrollBarVisibility="Auto"
MaxHeight="95">
</ListBox>
In the View Model I have the following:
public IEnumerable<SelectedClientVM> SelectedClients
....
public class SelectedClientVM
{
public bool IsSelected { get; set; }
public Client Client { get; set; }
public override string ToString()
{
return Client.SearchText;
}
}
I think what you want can be better achieved by defining a DataTemplate to be used for each item in the ListBox. A DataTemplate specifies how you want an individual piece of data (a Client in your case) rendered in the ListBox.
Here's my XAML for a simple DataTemplate.
<DataTemplate x:Key="clientTemplate" DataType="{x:Type local:Client}">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition />
</Grid.ColumnDefinitions>
<CheckBox IsChecked="{Binding IsSelected}" />
<TextBlock Grid.Column="1" Text="{Binding Name}" Margin="5,0,0,0" />
</Grid>
</DataTemplate>
Here's how I referenced it in the ListBox declaration:
<ListBox ItemsSource="{Binding SelectedClients}"
VirtualizingStackPanel.IsVirtualizing="True"
ItemTemplate="{StaticResource clientTemplate}" />
Secondly, to Grant's answer, you'll want to be sure that your Client class implements INotifyPropertyChanged. Plus, you'll want to expose your list of Clients using a collection that supports change notifications. I usually use ObservableCollection<T>.
This may not be the only issue, but if you want the view to update based on your ViewModel you'll have to implement INotifyPropertyChanged (or something that does a similar job) on your IsSelected property.
I have a list box that displays the results of a TFS Query. I want to change the style of the ListBoxItem in the code behind to have the columns that are included in the query results.
The style for the ListBoxItem is defined in my Windows.Resoruces Section. I have tried this:
public T GetQueryResultsElement<T>(string name) where T : DependencyObject
{
ListBoxItem myListBoxItem =
(ListBoxItem)(lstQueryResults.ItemContainerGenerator.ContainerFromIndex(0));
// Getting the ContentPresenter of myListBoxItem
ContentPresenter myContentPresenter =
myListBoxItem.Template.LoadContent().FindVisualChild<ContentPresenter>();
// Finding textBlock from the DataTemplate that is set on that ContentPresenter
DataTemplate myDataTemplate = myContentPresenter.ContentTemplate; <------+
T myControl = (T)myDataTemplate.FindName(name, myContentPresenter); |
|
return (T)myControl; |
} |
|
ContentTemplate is null ----------------------------------------------+
But the ContentTemplate is null. I got that code from here, then modified it with the LoadContent call (the orginal code gave null for the ContentPresenter).
Anyway. If you know a way to change an existing style in the code behind I would love to see it.
Specifics if you want them:
I am going for WrapPanel in my ListBoxItem Style. This is what I want to add the extra TextBlock items to.
Here is part of my style:
<!--Checkbox ListBox-->
<Style x:Key="CheckBoxListStyle" TargetType="ListBox">
<Style.Resources>
<Style x:Key="ListBoxItemStyle" TargetType="ListBoxItem">
<Setter Property="Tag" Value="{Binding Id}"/>
<Setter Property="Background">
<Setter.Value>
<Binding Path="Type" Converter="{StaticResource WorkItemTypeToColorConverter}" />
</Setter.Value>
</Setter>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="ListBoxItem">
<Border BorderThickness="1" BorderBrush="#D4D4FF">
<Grid Width="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type WrapPanel}}, Path=ActualWidth}" ScrollViewer.CanContentScroll="True" Margin="2">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="20" />
<ColumnDefinition Width="*" />
<ColumnDefinition Width="30" />
</Grid.ColumnDefinitions>
<Grid.Background>
<Binding Path="Type" Converter="{StaticResource WorkItemTypeToColorConverter}" />
</Grid.Background>
<CheckBox VerticalAlignment="Center" Grid.Column="0" IsChecked="{Binding IsSelected,
RelativeSource={RelativeSource TemplatedParent},
Mode=TwoWay}" Name="chkIsSelected" />
<WrapPanel Grid.Column="1" Margin="5,0,5,0" Name="QueryColumns">
<TextBlock VerticalAlignment="Center" Text="{Binding Id}" Name="txtID" />
<TextBlock VerticalAlignment="Center" Margin="5,0,5,0" Text="{Binding Title}" Name="txtTitle" />
</WrapPanel>
You're going against the grain here, trying to manipulate visual elements directly in code-behind. There's a much simple solution involving data binding.
I'll provide the general solution because I don't know the specifics of your solution.
Once you get your query results, create an enumeration that returns a column name, and a field value for each iteration.
Example:
class NameValuePair
{
public string Name { get; set; }
public object Value { get; set; }
}
public IEnumerable<IEnumerable<NameValuePair>> EnumerateResultSet(DataTable resultSet)
{
foreach (DataRow row in resultSet.Rows)
yield return EnumerateColumns(resultSet, row);
}
public IEnumerable<NameValuePair> EnumerateColumns(DataTable resultSet, DataRow row)
{
foreach (DataColumn column in resultSet.Columns)
yield return new NameValuePair
{ Name = column.ColumnName, Value = row[column] };
}
And in your code-behind, once you get your DataTable result set, do this:
myResultsList.ItemsSource = EnumerateResultSet(myDataTable);
The XAML might look like this:
<Window.Resources>
<DataTemplate x:Key="ColumnTemplate">
<Border BorderBrush="Black" BorderThickness="1" CornerRadius="2" Padding="2">
<WrapPanel>
<TextBlock Text="{Binding Name}" Margin="0,0,5,0"/>
<TextBlock Text="{Binding Value}" Margin="0,0,10,0"/>
</WrapPanel>
</Border>
</DataTemplate>
<DataTemplate x:Key="RowTemplate">
<Grid>
<ItemsControl
ItemsSource="{Binding}"
ItemTemplate="{StaticResource ColumnTemplate}"
Margin="0,5,0,5"/>
</Grid>
</DataTemplate>
</Window.Resources>
<Grid>
<ListBox Name="myResultsList" ItemTemplate="{StaticResource RowTemplate}"/>
</Grid>
Sample output: