wpf combobox not binding to collection when inside ContentTemplate - wpf

I'm trying show a certain control based on a property (combobox or textbox). So I have this contentcontrol implemented:
<!--<ComboBox MaxWidth="200" Background="#333333" ItemsSource="{Binding ModelObjectWrapper.Values}" Grid.Row="1" Grid.Column="1"/>-->
<ContentControl Grid.Row="1" Grid.Column="1">
<ContentControl.Resources>
<Style TargetType="ContentControl">
<Style.Triggers>
<DataTrigger Binding="{Binding ModelObjectWrapper.ObjType}" Value="typeA">
<Setter Property="ContentTemplate">
<Setter.Value>
<DataTemplate>
<ComboBox HorizontalAlignment="Left" MaxWidth="200" Background="#333333" ItemsSource="{Binding ModelObjectWrapper.Values, UpdateSourceTrigger=PropertyChanged}"/>
</DataTemplate>
</Setter.Value>
</Setter>
</DataTrigger>
<DataTrigger Binding="{Binding ModelObjectWrapper.ObjType}" Value="typeB">
<Setter Property="ContentTemplate">
<Setter.Value>
<DataTemplate>
<TextBox />
</DataTemplate>
</Setter.Value>
</Setter>
</DataTrigger>
</Style.Triggers>
</Style>
</ContentControl.Resources>
</ContentControl>
The problem is that the combobox is not showing any items when its part of a controltemplate, and I know the bound list does have them, so I'm assuming the combobox is not being bound to the ItemsSource correctly.
The first line (having only a combobox without the templates), commented out, works fine. Am I not doing the binding right? Could it be that because its part of a datatrigger, it's not getting the right DataContext? I must note that the DataTrigger itself works great (IE showing a combobox if *.ObjType == "typeA".
The VM is a wrapper class around an object:
public class ModelObjectWrapper : ViewModelBase
{
private theModelObject model_obj;
public ModelObjectWrapper(theModelObject obj)
{
model_obj = obj;
}
public ObservableCollection<string> Values
{
get { return model_obj.Values; }
set
{
if (value == model_obj.Values)
return;
model_obj.Values = value;
OnPropertyChanged();
}
}
}

The DataContext of the root element in a ContentControl is the Content of the same ContentControl. Try to use a RelativeSource to bind to a property of the ContentControl's DataContext:
<ComboBox HorizontalAlignment="Left" MaxWidth="200" Background="#333333"
ItemsSource="{Binding DataContext.ModelObjectWrapper.Values, RelativeSource={RelativeSource AncestorType=ContentControl}}"/>
By the way, there is no point setting the UpdateSourceTrigger of an ItemsSource binding to PropertyChanged because the ComboBox never sets the source property.

Related

How to make common template with dynamic binding in WPF

I have created different controls in WPF with their respective control templates in which styling is almost similar but binding is different, as given below removing extra clutter. I am looking for a way to make a common ControlTemplate with some way to make the binding dynamic.
ControlTemplate for MasterRuleLayout control
<ControlTemplate TargetType="{x:Type local:MasterRuleLayout}">
<StackPanel>
<Image
Style="{StaticResource MasterLayoutImageStyle}"
DataContext="{Binding CommonAggregate.SelectedRule}">
</Image>
<TextBox
Text="{Binding CommonAggregate.SelectedRule.Name}">
<TextBox.Style>
<Style TargetType="TextBox">
<Style.Triggers>
<DataTrigger Binding="{Binding CommonAggregate.SelectedRule.Parent}"
Value="{x:Null}">
<Setter Property="IsEnabled" Value="False" />
</DataTrigger>
</Style.Triggers>
</Style>
</TextBox.Style>
</TextBox>
</StackPanel>
</ControlTemplate>
ControlTemplate for MasterEntityLayout control
<ControlTemplate TargetType="{x:Type local:MasterEntityLayout}">
<StackPanel>
<Image
Style="{StaticResource MasterLayoutImageStyle}"
DataContext="{Binding CommonAggregate.SelectedEntityItem}">
</Image>
<TextBox
Text="{Binding CommonAggregate.SelectedEntityItem.Name}">
</TextBox>
</StackPanel>
</ControlTemplate>
Bindings need dependency properties to form the "glue" between the properties in your template bindings and the properties in the view models that you're binding to. That means your options are 1) use TemplateBinding to bind via existing properties in the templated parent, 2) create a custom control with any additional properties that are missing, or 3) use attached properties:
<Window.Resources>
<ControlTemplate x:Key="MyTemplate" TargetType="{x:Type Control}">
<TextBlock Text="{Binding Path=(local:AttachedProps.Name), Mode=OneWay,
RelativeSource={RelativeSource TemplatedParent}}" />
</ControlTemplate>
</Window.Resources>
<Control Template="{StaticResource MyTemplate}"
local:AttachedProps.Name="{Binding MyViewModelName, Mode=OneWay}" />
And then you would create the attached property itself like so:
public static class AttachedProps
{
#region Name
public static string GetName(DependencyObject obj)
{
return (string)obj.GetValue(NameProperty);
}
public static void SetName(DependencyObject obj, string value)
{
obj.SetValue(NameProperty, value);
}
public static readonly DependencyProperty NameProperty =
DependencyProperty.RegisterAttached("Name", typeof(string),
typeof(AttachedProps), new PropertyMetadata(String.Empty));
#endregion Name
}

WPF MVVM-Light adding an image to a listview item when selecting

I have a ListView that I want to be able to add a 'Delete' button to, with a command attached to it, when I select an item. I can't find anything about this on stack which is quite surprising.
One way you could do this is by applying a style to the ListViewItem in the ListView.Resources. In the Style, you set the ContentTemplate to a DataTemplate with only a TextBlock. Then you give the Style a Trigger that is tied to the IsSelected property. In the Trigger you'll set the ContentTemplate to a new DataTemplate with a TextBlock and a Button. The Command property of the Button is bound to the ViewModel using the RelativeSource binding and the CommandParameter is bound to the item so that it gets passed along as a parameter to your DeleteCommand.
XAML
<ListView Margin="3"
MinWidth="200"
ItemsSource="{Binding Items}">
<ListView.Resources>
<Style TargetType="{x:Type ListViewItem}">
<Setter Property="ContentTemplate">
<Setter.Value>
<DataTemplate>
<TextBlock Text="{Binding SomeProperty}"
Margin="3" />
</DataTemplate>
</Setter.Value>
</Setter>
<Style.Triggers>
<Trigger Property="IsSelected"
Value="True">
<Setter Property="ContentTemplate">
<Setter.Value>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding SomeProperty}"
Margin="3" />
<Button Content="Delete"
Margin="3"
Command="{Binding DataContext.DeleteCommand, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type ListView}}}"
CommandParameter="{Binding }" />
</StackPanel>
</DataTemplate>
</Setter.Value>
</Setter>
</Trigger>
</Style.Triggers>
</Style>
</ListView.Resources>
</ListView>
ViewModel
private ICommand _DeleteCommand;
public ICommand DeleteCommand
{
get
{
if (_DeleteCommand == null)
{
_DeleteCommand = new RelayCommand<ExampleModel>(param => DeleteItem(param));
}
return _DeleteCommand;
}
}
private void DeleteItem(ExampleModel item)
{
MessageBox.Show(item.SomeProperty);
}
I just have the Delete method showing the value in a MessageBox for demo purposes. You should be able to modify the DataTemplate and Delete method to meet your needs.

Change WPF UserControl depending on a Property of a TreeViewItem

My application shows a TreeView on the left with hierarchical ordered items which are all the same type. All the items have a dependency property which can have one of two values. This value is an enum. Depending on this value I want to show one of two UserControls on the left. My idea was to insert both controls and set their opacity to 0. Then I wanted to insert a Style with a DataTrigger that triggers the opacity depending on the enum's value. But I can not access the properties of one control from the other control's DataTrigger; and the trigger does not seem to recognize the enum's value.
The enum:
public enum IdentityType
{
Person,
OrganisationUnit
}
The XAML:
<TreeView Grid.Column="0" Grid.Row="1" Background="AntiqueWhite" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" ItemsSource="{Binding Identities}" x:Name="OiTree">
<TreeView.ItemTemplate>
<HierarchicalDataTemplate ItemsSource="{Binding Childs}">
<TextBlock Text="{Binding}"/>
</HierarchicalDataTemplate>
</TreeView.ItemTemplate>
</TreeView>
<Controls:UcPerson Grid.Column="1" Grid.Row="1" Opacity="0">
<Controls:UcPerson.Style>
<Style TargetType="Controls:UcPerson">
<Style.Triggers>
<DataTrigger Binding="{Binding Path=Type, ElementName=OiTree.SelectedItem}" Value="Person">
<Setter Property="Opacity" Value="1"/>
</DataTrigger>
</Style.Triggers>
</Style>
</Controls:UcPerson.Style>
</Controls:UcPerson>
<Controls:UcOrgUnit Grid.Column="1" Grid.Row="1" Opacity="0">
<Controls:UcOrgUnit.Style>
<Style TargetType="Controls:UcOrgUnit">
<Style.Triggers>
<DataTrigger Binding="{Binding Path=Type, ElementName=OiTree.SelectedItem}" Value="OrganisationUnit">
<Setter Property="Opacity" Value="1"/>
</DataTrigger>
</Style.Triggers>
</Style>
</Controls:UcOrgUnit.Style>
</Controls:UcOrgUnit>
The problem is that you are setting the Opacity directly on the control first.
An explicit setting on a control will always override a trigger value.
However, a trigger value will override a style setter.
The following code should work (though I haven't tested it myself)
<Controls:UcPerson Grid.Column="1" Grid.Row="1">
<Controls:UcPerson.Style>
<Style TargetType="Controls:UcPerson">
<Style.Setters>
<Setter Property="Opacity" Value="0" />
</Style.Setters>
<Style.Triggers>
<DataTrigger Binding="{Binding Path=Type, ElementName=OiTree.SelectedItem}" Value="Person">
<Setter Property="Opacity" Value="1"/>
</DataTrigger>
</Style.Triggers>
</Style>
</Controls:UcPerson.Style>
See this question here for another example of the problem: DataTrigger not firing
As an aside, I believe your problem could be solved more elegantly using a DataTemplateSelector.
As Andrew implied the solution to my problem was a DataTemplateSelector. Instead of two userControls I created two templates and Used a ContentControl. The content Property of the ContentControl is bound to the SelectedItem of the TreeView and I implemented a simple DataTemplateSelector which casts the content to the original object an decides wich template to use. The source (modified) is from here: link
This is the xaml:
<Window.Resources>
<DataTemplate x:Key="borderTemplate">
<Border BorderThickness="1" BorderBrush="Brown" CornerRadius="5">
<TextBlock Margin="5" Text="Border Template"/>
</Border>
</DataTemplate>
<DataTemplate x:Key="twoTextBlockTemplate">
<StackPanel>
<TextBlock Margin="5" Text="First TextBlock"/>
<TextBlock Margin="5" Text="Second TextBlock"/>
</StackPanel>
</DataTemplate>
<vm:OiContentTemplateSelector
x:Key="myContentTemplateSelector"
BorderTemplate="{StaticResource borderTemplate}"
TwoTextBlockTemplate="{StaticResource twoTextBlockTemplate}"/>
</Window.Resources>
This is the DataTemplateSelector:
public class OiContentTemplateSelector : DataTemplateSelector
{
public DataTemplate BorderTemplate
{ get; set; }
public DataTemplate TwoTextBlockTemplate
{ get; set; }
public override DataTemplate SelectTemplate(object item, DependencyObject container)
{
OrganisationIdentity value = item as OrganisationIdentity;
if (value != null)
{
if (value.Type == IdentityType.Person)
return BorderTemplate;
else if (value.Type == IdentityType.OrganisationUnit)
return TwoTextBlockTemplate;
return base.SelectTemplate(item, container);
}
else
return base.SelectTemplate(item, container);
}
}
Perhaps this helps somebody

WPF ListBox selection issues, providing feedback to user

I'm trying to create a listbox that shows a thumbnail view of page content, for an app with one canvas but multiple 'pages'. Yes, that's probably not the best place to start from but for historical reasons that's what I have.
I've implemented the ListBox with data binding to a singleton 'WorkBook' that has an ObservableCollection of PageData (everything that appears on a page, including it's background).
What I really really want is to be able to change the Border colour of a ListBoxItem when it's selected and keep that Border colour while it's content is the currently selected item in the class that hosts the collection.
My problems are:-
1/ I can't get the ListBox to select the 1st item on program startup.
2/ when the ListBox loses focus the SelectedIndex is always -1 (so no selection)
3/ adding to the ListBox results in no selection (SelectedIndex == -1)
4/ using triggers, I can set the border of the selectedItem but this is lost when the ListBox loses focus. As the ListBoxItem shows an image that is opaque the 'standard' way of making selection colours stay when out of focus doesn't work - ie
<Style x:Key="PsThumb">
<Style.Resources>
<SolidColorBrush x:Key="{x:Static SystemColors.HighlightBrushKey}" Color="White"></SolidColorBrush>
<SolidColorBrush x:Key="{x:Static SystemColors.ControlBrushKey}" Color="White"></SolidColorBrush>
</Style.Resources>
</Style>
My code for the ListBox is as follows :-
<ListBox x:Name="PageSorter" Style="{StaticResource PsThumb}" Width="148" BorderThickness="4" HorizontalContentAlignment="Stretch" ScrollViewer.HorizontalScrollBarVisibility="Disabled"
VerticalAlignment="Stretch" ItemsSource="{Binding Pages,Source={StaticResource WorkBook}}" SelectedItem="{Binding Path=CurrentPageData, Mode=TwoWay}"
AllowDrop="True" ScrollViewer.VerticalScrollBarVisibility="Auto">
<ListBox.ItemTemplate>
<DataTemplate>
<Grid>
<Border x:Name="border" BorderBrush="DarkGray" BorderThickness="4" Margin="2,4,2,4" CornerRadius="5">
<Border.Effect>
<DropShadowEffect ShadowDepth="6"/>
</Border.Effect>
<Image Source="{Binding Thumbnail}" Width="130" Height="95" Stretch="Fill"/>
</Border>
</Grid>
<DataTemplate.Triggers>
<DataTrigger Binding="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type ListBoxItem}},Path=IsSelected}" Value="True">
<Setter TargetName="border" Property="BorderBrush" Value="Red"></Setter>
</DataTrigger>
</DataTemplate.Triggers>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
The simplest way to achieve what you're asking is to bind the DataTrigger to a property inside the items of the ListBox. In the example below, I added the Selected property on my Contact class:
public class Contact : INPCBase
{
public string Name { get; set; }
private bool _Selected;
public bool Selected
{
get { return _Selected; }
set
{
_Selected = value;
NotifyPropertyChanged("Selected");
}
}
}
I then bind my ListBox to a List<Contact>. When the user checks the CheckBox, we change the Selected property and in turn trigger the Style:
<Window x:Class="WpfApp.ListboxKeepSelectionWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="ListboxKeepSelectionWindow" Height="277" Width="343"
xmlns:me="clr-namespace:WpfApp">
<Window.Resources>
<me:ContactList x:Key="sample"/>
<Style TargetType="ListBoxItem" x:Key="SelectedListBoxItemStyle">
<Style.Triggers>
<DataTrigger Binding="{Binding Path=Selected}" Value="True">
<Setter Property="BorderBrush" Value="Orange"/>
</DataTrigger>
</Style.Triggers>
</Style>
</Window.Resources>
<Grid>
<ListBox Name="lbxContacts"
ItemsSource="{StaticResource ResourceKey=sample}"
SelectionMode="Extended"
ItemContainerStyle="{StaticResource ResourceKey=SelectedListBoxItemStyle}"
SelectionChanged="lbxContacts_SelectionChanged">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel>
<CheckBox IsChecked="{Binding Path=Selected}">
<TextBlock Text="{Binding Path=Name}"/>
</CheckBox>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</Grid>
To allow the user to select items without using the checkbox, I added this small event on the ListBox. Since the Contact class implements the INotifyPropertyChanged interface, the value of the CheckBox is updated so the user knows the selection worked:
private void lbxContacts_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
foreach (Contact c in e.AddedItems)
{
c.Selected = !c.Selected;
}
}
When you want to get a list of selected items, you just use a LINQ query on the ItemsSource to get items where Selected == true.

Pass listbox selected item information through to usercontrol (WPF)

I have a listbox, and each listbox item is a custom usercontrol I made. I have used styles to remove all of the default highlighting for a listbox item (ie. removing the blue background highlight for a selected item).
What I want is to be able to do something special to my user control to denote that the listbox item is highlighted. Such as make the border on the user control more bold, something like that.
If I could get a boolean into the user control, I think from there I'd be able to figure out how to make the necessary changes to the user control... through a converter or something most likely.
What I'm not sure of, is how do I pass into the usercontrol the information that shows whether the listbox item which the user control is in is highlighted.
The code in question is like this:
<ListBox.ItemTemplate>
<DataTemplate>
<hei:OrangeUserCtrl DataContext="{Binding}" Height="40" Width="40" />
</DataTemplate>
</ListBox.ItemTemplate>
How can I pass in to the user control (preferably as a true/false) if the listbox item it is in is highlighted?
Thanks
You can use Tag property and RelativeSource binding.
In my example when item is highlighted I changed Border properties (BorderBrush=Red and BorderThickness=3).
Source code:
Simple class to hold data:
class Person
{
public string Name { get; set; }
public string Surname { get; set; }
}
ListBox:
<ListBox ItemsSource="{Binding}">
<ListBox.ItemTemplate>
<DataTemplate>
<local:MyCustomPresenter DataContext="{Binding}"
Tag="{Binding Path=IsSelected, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=ListBoxItem}, UpdateSourceTrigger=PropertyChanged}"
Height="60" Width="120" />
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
UserControl to display custom data:
<UserControl x:Class="WpfTextWrapping.MyCustomPresenter"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300">
<Border Margin="10">
<Border.Style>
<Style TargetType="Border">
<Setter Property="BorderBrush" Value="Green" />
<Setter Property="BorderThickness" Value="1" />
<Style.Triggers>
<DataTrigger Binding="{Binding Path=Tag, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=UserControl}, UpdateSourceTrigger=PropertyChanged}" Value="True">
<Setter Property="BorderBrush" Value="Red" />
<Setter Property="BorderThickness" Value="3" />
</DataTrigger>
</Style.Triggers>
</Style>
</Border.Style>
<StackPanel Orientation="Vertical">
<TextBlock Text="{Binding Name}" />
<TextBlock Text="{Binding Surname}" />
</StackPanel>
</Border>
</UserControl>
If I understand you well, you need to add a property to your custom UserControl bound to the nested ComboBox something like :
public object MySelectedItem
{
get { return myNestedCombox.SelectedItem; }
set { myNestedCombox.SelectedItem = value; }
}
You need to NotifyPropertyChanged as well.

Resources