binding to nested object from the parent user control wpf - c#

I am writing a new user control. It needs to be able to display an ObservableCollection of items. Those items will have a property that is also an observable collection, so it is similar to a 2-d jagged array. The control is similar to a text editor so the outer collection would be the lines, the inner collection would be the words. I want the consumer of the control to be able to specify not only the binding for the lines, but also the binding for the words. The approach I have so far is as follows:
The user control inherits from ItemsControl. Inside this control it has a nested ItemsControl. I would like to be able to specify the binding path of this nested ItemsControl from the parent user control. The XAML for the UserControl is
<ItemsControl x:Class="IntelliDoc.Client.Controls.TextDocumentEditor"
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"
xmlns:local="clr-namespace:IntelliDoc.Client"
xmlns:con="clr-namespace:IntelliDoc.Client.Controls"
xmlns:data="clr-namespace:IntelliDoc.Data;assembly=IntelliDoc.Data"
xmlns:util="clr-namespace:IntelliDoc.Client.Utility"
xmlns:vm="clr-namespace:IntelliDoc.Client.ViewModel"
xmlns:sys="clr-namespace:System;assembly=mscorlib"
xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
mc:Ignorable="d"
x:Name="root"
d:DesignHeight="300" d:DesignWidth="300"
>
<ItemsControl.Template>
<ControlTemplate>
<StackPanel Orientation="Vertical">
<ItemsPresenter Name="PART_Presenter" />
</StackPanel>
</ControlTemplate>
</ItemsControl.Template>
<ItemsControl.ItemTemplate>
<DataTemplate >
<StackPanel Orientation="Horizontal">
<ItemsControl Name="PART_InnerItemsControl" ItemsSource="{Binding NestedBinding, ElementName=root}" >
<ItemsControl.Template>
<ControlTemplate>
<StackPanel Name="InnerStackPanel" Orientation="Horizontal" >
<TextBox Text="" BorderThickness="0" TextChanged="TextBox_TextChanged" />
<ItemsPresenter />
</StackPanel>
</ControlTemplate>
</ItemsControl.Template>
<ItemsControl.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal" >
<ContentControl Content="{Binding Path=Data, Mode=TwoWay}" />
<TextBox BorderThickness="0" TextChanged="TextBox_TextChanged" />
</StackPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</StackPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
The code behind has this property declared
public partial class TextDocumentEditor : ItemsControl
{
public static readonly DependencyProperty NestedItemsProperty = DependencyProperty.Register("NestedItems", typeof(BindingBase), typeof(TextDocumentEditor),
new PropertyMetadata((BindingBase)null));
public BindingBase NestedItems
{
get { return (BindingBase)GetValue(NestedItemsProperty); }
set
{
SetValue(NestedItemsProperty, value);
}
}
...
}
The expected bound object will be as follows:
public class ExampleClass
{
ObservableCollection<InnerClass> InnerItems {get; private set;}
}
public class InnerClass : BaseModel //declares OnPropertyChanged
{
private string _name;
public string Name //this is provided as an example property and is not required
{
get
{
return _name;
}
set
{
_name = value;
OnPropertyChanged(nameof(Name));
}
}
....
}
public class ViewModel
{
public ObservableCollection<ExampleClass> Items {get; private set;}
}
The XAML declaration would be as follows:
<Window x:Class="IntelliDoc.Client.TestWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:sys="clr-namespace:System;assembly=mscorlib"
mc:Ignorable="d"
Title="TestWindow" Height="300" Width="300">
<DockPanel>
<TextDocumentEditor ItemsSource="{Binding Path=Items}" NestedItems={Binding Path=InnerItems} >
<DataTemplate>
<!-- I would like this to be the user defined datatemplate for the nested items. Currently I am just declaring the templates in the resources of the user control by DataType which also works -->
</DataTemplate>
</TextDocumentEditor>
</DockPanel>
In the end, I want the user control I created to provide the ItemsControl template at the outer items level, but I want the user to be able to provide the datatemplate at the inner items control level. I want the consumer of the control to be able to provide the bindings for both the Outer items and the nested items.

I was able to come up with a solution that works for me. There may be a better approach, but here is what I did.
First, on the outer ItemsControl, I subscribed to the StatusChanged of the ItemContainerGenerator. Inside that function, I apply the template of the ContentPresenter and then search for the Inner ItemsControl. Once found, I use the property NestedItems to bind to the ItemsSource property. One of the problems I was having originally was I was binding incorrectly. I fixed that and I changed the NestedItems to be a string. Also, I added a new property called NestedDataTemplate that is of type DataTemplate so that a user can specify the DataTemplate of the inner items control. It was suggested that I not use a UserControl since I don't inherit from a UserControl, so I will change it to a CustomControl. The code changes are below
public static readonly DependencyProperty NestedItemsProperty = DependencyProperty.Register("NestedItems", typeof(string), typeof(TextDocumentEditor),
new PropertyMetadata((string)null));
public static readonly DependencyProperty NestedDataTemplateProperty = DependencyProperty.Register("NestedDataTemplate", typeof(DataTemplate), typeof(TextDocumentEditor),
new PropertyMetadata((DataTemplate)null));
public DataTemplate NestedDataTemplate
{
get { return (DataTemplate)GetValue(NestedDataTemplateProperty); }
set
{
SetValue(NestedDataTemplateProperty, value);
}
}
public string NestedItems
{
get { return (string)GetValue(NestedItemsProperty); }
set
{
SetValue(NestedItemsProperty, value);
}
}
private void ItemContainerGenerator_StatusChanged(object sender, EventArgs e)
{
if (((ItemContainerGenerator)sender).Status != GeneratorStatus.ContainersGenerated)
return;
ContentPresenter value;
ItemsControl itemsControl;
for (int x=0;x<ItemContainerGenerator.Items.Count; x++)
{
value = ItemContainerGenerator.ContainerFromIndex(x) as ContentPresenter;
if (value == null)
continue;
value.ApplyTemplate();
itemsControl = value.GetChildren<ItemsControl>().FirstOrDefault();
if (itemsControl != null)
{
if (NestedDataTemplate != null)
itemsControl.ItemTemplate = NestedDataTemplate;
Binding binding = new Binding(NestedItems);
BindingOperations.SetBinding(itemsControl, ItemsSourceProperty, binding);
}
}
}

Related

Why are listbox items not being unselected if they are not in view, after the listbox itself comes back into view?

This is a weird issue that I found in an MVVM project. I bound the IsSelected property of a ListBoxItem to an IsSelected property in the underlying model. If the collection holding the models bound to the list is too big, when you select a different user control and the focus is taken off of the ListBox; when you select an item in the list it will unselect every item EXCEPT the ones that are off-screen. The following gif shows this issue in a test project I made specifically for this issue;
MainView.xaml
<UserControl x:Class="ListBox_Selection_Issue.Views.MainView"
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"
xmlns:local="clr-namespace:ListBox_Selection_Issue.Views"
xmlns:vms="clr-namespace:ListBox_Selection_Issue.ViewModels"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800">
<UserControl.DataContext>
<vms:MainViewModel/>
</UserControl.DataContext>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<ListBox Grid.Row="0" SelectionMode="Extended" ItemsSource="{Binding FirstCollection}">
<ListBox.ItemContainerStyle>
<Style TargetType="{x:Type ListBoxItem}">
<Setter Property="IsSelected" Value="{Binding IsSelected, Mode=TwoWay}" />
</Style>
</ListBox.ItemContainerStyle>
<ListBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Name}" />
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
<Button Grid.Row="1"/>
</Grid>
</UserControl>
MainViewModel.cs
using System.Collections.ObjectModel;
namespace ListBox_Selection_Issue.ViewModels
{
class MainViewModel : ObservableObject
{
private ObservableCollection<CustomClass> _firstCollection;
public ObservableCollection<CustomClass> FirstCollection
{
get { return _firstCollection; }
set
{
_firstCollection = value;
OnPropertyChanged("FirstCollection");
}
}
public MainViewModel()
{
ObservableCollection<CustomClass> first = new ObservableCollection<CustomClass>();
for (int i = 1; i <= 300; i++)
{
first.Add(new CustomClass($"{i}"));
}
FirstCollection = first;
}
}
public class CustomClass
{
public string Name { get; set; }
public bool IsSelected { get; set; }
public CustomClass(string name)
{
Name = name;
IsSelected = false;
}
}
}
This is not how it works. If you understand UI virtualization, you should understand that virtualized containers (in your case ListBoxItem) are not part of the visual tree as they are removed as part of the virtualization process.
Because the WPF rendering engine has now far less containers to render, the performance is significantly improved. The effect becomes more relevant the more items the ItemsControl holds.
This is why you would never want to disable UI virtualization. This is why your posted solution can be qualified as a bad solution one should avoid.
ListBox is a Selector control. To allow it to work with any data, it must not be aware of the actual data models it renders. That's what the containers are for: anonymous wrappers that allow rendering and interaction logic without having the host to know the wrapped data object.
In your case, when ListBox.SelectionMode is set to SelectionMode.Extended or SelectionMode.Multiple, the ListBox will have to unselect all previously selected item in case the selection changes. Since it doesn't care about your data models, it only handles their associated wrappers: it will iterate over all ListBoxItem instances to change their state to e.g. unselected.
But the selection state will only be forwarded to the binding source for those Binding objects that are actually active (because the binding target is currently realized/visible).
Although all containers, virtualized and realized, will be unselected, the Binding of those virtualized containers won't update (because the corresponding container is not active and removed - removing includes clearing their ListBoxItem.Content property too).
As a matter of fact, unless container recycling is explicitly enabled by setting VirtualizingPanel.VirtualizationMode to VirtualizationMode.Recycling, virtualized container instances are removed by simply making them eligible for garbage collection. They are just dropped and gone without any further modification e.g., of the ListBoxItem.IsSelected property and new container instances are created for newly realized items.
Now when you scroll the virtualized items into the view, the ListBox will generate the containers and will set the ListBoxItem.Content property to the wrapped data item. Since in your case the data model, the CustomClass, still holds the previous and now outdated selection state (which is still "selected"), the realized containers will change their state back to selected (via the reactivated data binding).
That's why in your case virtualized items remain selected. And this is because you bind the container's state to your data model in a multiselect scenario with UI virtualization engaged.
ListBox selection states are meant to be handled via the Selector.SelectedItem and ListBox.SelectedItems properties or the Selector.SelectionChanged event. This is not important in single select mode but essential in multiselect mode.
Since ListBox.SelectedItems is a read-only property, you can't set a binding on it (like you would usually do with the SelectedItem property).
There are many ways you can get the SeletedItems value to your DataContext. The most straight forward would be to send it from the Selector.SelectionChanged event.
From a design perspective, you should generally never set the DataContext of a UserControl, or Control in general, explicitly. The DataContext should be inherited from the parent element that hosts your custom control.
A View-Model-per-View approach is always to avoid. It will make your control very specific to a particular data type. And vice versa, it will make your View Model too specific for a particular element of the View. Additionally, it will introduce other design problems that may even lead to break MVVM.
The DataContext must always be set outside the control (from the client context), so that the control doesn't know the concrete DataContext class.
The improved solution could look as follows:
MainWindow.xaml
<Window>
<Window.DataContext>
<local:MainViewModel />
</Window.DataContext>
<Grid>
<local:MainView ItemsSource="{Binding FirstCollection}"
SelectedItems="{Binding SelectedCustomClassModels}">
<local:MainView.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Name}" />
</DataTemplate>
</local:MainView.ItemTemplate>
</local:MainView>
</Grid>
</Window>
MainView.xaml.cs
public partial class MainView : UserControl
{
public IList SelectedItems
{
get => (IList)GetValue(SelectedItemsProperty);
set => SetValue(SelectedItemsProperty, value);
}
public static readonly DependencyProperty SelectedItemsProperty = DependencyProperty.Register(
"SelectedItems",
typeof(IList),
typeof(MainView),
new FrameworkPropertyMetadata(default(IList), FrameworkPropertyMetadataOptions.BindsTwoWayByDefault));
public IList ItemsSource
{
get => (IList)GetValue(ItemsSourceProperty);
set => SetValue(ItemsSourceProperty, value);
}
public static readonly DependencyProperty ItemsSourceProperty = DependencyProperty.Register(
"ItemsSource",
typeof(IList),
typeof(MainView), new PropertyMetadata(default));
public DataTemplate ItemTemplate
{
get => (DataTemplate)GetValue(ItemTemplateProperty);
set => SetValue(ItemTemplateProperty, value);
}
public static readonly DependencyProperty ItemTemplateProperty = DependencyProperty.Register(
"ItemTemplate",
typeof(DataTemplate),
typeof(MainView), new PropertyMetadata(default));
public MainView()
{
InitializeComponent();
}
private void ListBox_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
// You can track particular items...
IList newSelectedItems = e.AddedItems;
IList newUnselectedItems = e.RemovedItems;
// ... or the final result
var listBox = sender as ListBox;
this.SelectedItems = listBox.SelectedItems;
}
}
MainView.xaml
<UserControl>
<Grid>
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition />
</Grid.RowDefinitions>
<ListBox Grid.Row="0"
SelectionMode="Extended"
SelectionChanged="ListBox_SelectionChanged"
ItemsSource="{Binding RelativeSource={RelativeSource AncestorType=UserControl}, Path=ItemsSource}"
ItemTemplate="{Binding RelativeSource={RelativeSource AncestorType=UserControl}, Path=ItemTemplate}" />
<Button Grid.Row="1" />
</Grid>
</UserControl>
MainViewModel.cs
public class MainViewModel : INotifyPropertyChanged
{
public ObservableCollection<CustomClass> FirstCollection { get; private set; }
private IList selectedCustomClassModels;
public IList SelectedCustomClassModels
{
get => this.selectedCustomClassModels;
set
{
this.selectedCustomClassModels = value;
OnPropertyChanged();
OnSelectedCustomClassModelsChanged();
}
}
public MainViewModel()
{
this.FirstCollection = new ObservableCollection<CustomClass>();
for (int i = 1; i <= 300; i++)
{
this.FirstCollection.Add(new CustomClass($"{i}"));
}
this.SelectedCustomClassModels = new List<object>();
}
private void OnSelectedCustomClassModelsChanged()
{
// TODO::Handle selected items
IEnumerable<CustomClass> selectedItems = this.SelectedCustomClassModels
.Cast<CustomClass>();
}
}
Finding the fix to this issue took me longer than I would have expected, so I figured I would share my knowledge.
Add VirtualizingStackPanel.IsVirtualizing="False" to the ListBox like in the following file:
<UserControl x:Class="ListBox_Selection_Issue.Views.MainView"
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"
xmlns:local="clr-namespace:ListBox_Selection_Issue.Views"
xmlns:vms="clr-namespace:ListBox_Selection_Issue.ViewModels"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800">
<UserControl.DataContext>
<vms:MainViewModel/>
</UserControl.DataContext>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<ListBox Grid.Row="0" SelectionMode="Extended" VirtualizingStackPanel.IsVirtualizing="False" ItemsSource="{Binding FirstCollection}">
<ListBox.ItemContainerStyle>
<Style TargetType="{x:Type ListBoxItem}">
<Setter Property="IsSelected" Value="{Binding IsSelected, Mode=TwoWay}" />
</Style>
</ListBox.ItemContainerStyle>
<ListBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Name}" />
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
<Button Grid.Row="1"/>
</Grid>
</UserControl>
I found out that the issue had to do with "Virtualization" from this answer (Select Show more comments). Then I started looking into Listbox Virtualization. I lost the tab where I got the answer from. So, I can't credit them. Hopefully, this will help someone in the future.

Specify Binding of DependencyProperty in UserControl itself

Following up on my previous question (Change brushes based on ViewModel property)
In my UserControl I have have a DependencyObject. I want to bind that object to a property of my ViewModel. In this case a CarViewModel, property name is Status and returns an enum value.
public partial class CarView : UserControl
{
public CarStatus Status
{
get { return (CarStatus)GetValue(CarStatusProperty); }
set { SetValue(CarStatusProperty, value); }
}
public static readonly DependencyProperty CarStatusProperty =
DependencyProperty.Register("Status", typeof(CarStatus), typeof(CarView), new PropertyMetadata(OnStatusChanged));
private static void OnStatusChanged(DependencyObject obj, DependencyPropertyChangedEventArgs args)
{
var control = (CarView)obj;
control.LoadThemeResources((CarStatus)e.NewValue == CarStatus.Sold);
}
public void LoadThemeResources(bool isSold)
{
// change some brushes
}
}
<UserControl x:Class="MySolution.Views.CarView"
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"
xmlns:views="clr-MySolution.Views"
mc:Ignorable="d"
views:CarView.Status="{Binding Status}">
<UserControl.Resources>
</UserControl.Resources>
<Grid>
<TextBlock Text="{Binding Brand}"FontSize="22" HorizontalAlignment="Center" VerticalAlignment="Center"/>
</Grid>
<UserControl
Where do I need to specify this binding? In the root of the UserControl it gives an error:
The attachable property 'Status' was not found in type 'CarView'
In my MainWindow I bind the CarView using a ContentControl:
<ContentControl
Content="{Binding CurrentCar}">
<ContentControl.Resources>
<DataTemplate DataType="{x:Type viewmodel:CarViewModel}">
<views:CarView />
</DataTemplate>
</ContentControl.Resources>
</ContentControl>
My ViewModel:
[ImplementPropertyChanged]
public class CarViewModel
{
public Car Car { get; private set; }
public CarStatus Status
{
get
{
if (_sold) return CarStatus.Sold;
return CarStatus.NotSold;
}
}
}
your binding isn't well written. instead of writing views:CarView.Status="{Binding Status}" you should write only Status="{Binding Status}"
It seems that your Control is binding to itself.
Status is looked for in CarView.
You should have a line of code in your control CodeBehind like :
this.DataContext = new ViewModelObjectWithStatusPropertyToBindFrom();
Regards

WPF UserControl: Client-side validation of TargetType for custom styles?

I have a UserControl for multi-purpose use.
For the sake of simplicity, I'll show you the control first:
<UserControl x:Class="CompetitionAgent.View.UserControls.ExpandingButtonGrid"
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:DesignWidth="200" d:DesignHeight="200"
Margin="0" Padding="0" Width="Auto" Height="Auto" >
<StackPanel Name="stpBody" Style="{Binding Style}">
<Button x:Name="btnExpander" Content="{Binding ExpanderButtonText}"
Style="{Binding ExpandButtonStyle}"
HorizontalAlignment="Center" Click="btnExpander_Click"
Height="25" Width="Auto" />
<StackPanel x:Name="stpButtons" Orientation="Horizontal"
Style="{Binding PanelStyle}"
Margin="0">
</StackPanel>
</StackPanel>
</UserControl>
Controls stpBody, stpButtons and btnExpander all have styles bound by the DataContext. The fields look like this:
#region body styles
public Style Style { get; set; }
public Style ExpandButtonStyle { get; set; }
#endregion body styles
#region button pannel styles
public Style PanelStyle { get; set; }
public Style ButtonStyle { get; set; }
#endregion button pannel styles
So when this UserControl is used in another window, it would look somewhat like this:
<UserControls:ExpandingButtonGrid x:Name="ebgSchemeManager"
Style="{StaticResource ExpandingButtonGridStyle}"
PanelStyle="{StaticResource ExpandingButtonGridPanelStyle}"
ExpandButtonStyle="{StaticResource ExpandingButtonGridExpandButtonStyle}" />
I was wondering, is there a way to validate the TargetTypes of the StaticResource styles so that they are required to target a stackpanel or button respectively?
For example, style ExpandingButtonGridExpandButtonStyle could target a DockPanel, resulting in a XAML parse exception at runtime.
UPDATE - Summary and update on Depency Objects
I've just figured out what DepencyObjects are (hurray!).
For those who are wondering, you need to register the field so the property can be assigned to dynamically.
public static readonly DependencyProperty ExpandButtonStyleProperty =
DependencyProperty.Register("ExpandButtonStyle", typeof(Style), typeof(ExpandingButtonPanel));
public Style ExpandButtonStyle
{
get
{
return (Style)GetValue(ExpandButtonStyleProperty);
}
set
{
if (!typeof(Button).IsAssignableFrom(value.TargetType))
{
throw new ArgumentException("The target type is expected to be button");
}
SetValue(ExpandButtonStyleProperty, value);
}
}
Sure there is. You can verify the Style.TargetType in your setter. This will also be checked by the WPF Designer.
For example:
private Style _buttonStyle;
public Style ButtonStyle
{
get
{
return _buttonStyle;
}
set
{
if (!typeof(Button).IsAssignableFrom(value.TargetType))
{
throw new ArgumentException("The target type is expected to be Button");
}
_buttonStyle = value;
}
}

Binding data to custom UserControl

I want bind ObservableCollection my custom UserControl but when I want add new element and I almost done but when I want add new element to collection my app crash with no reason. I thought maybe it is variable inconsistency. But I came with nothig trying with object/String/string. Maybe I done something wrong at start?
Custom UserControl XAML
<UserControl x:Class="backtrackPrototype.checklistItem"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
FontFamily="{StaticResource PhoneFontFamilyNormal}"
FontSize="{StaticResource PhoneFontSizeNormal}"
Foreground="{StaticResource PhoneForegroundBrush}"
d:DesignHeight="80" d:DesignWidth="480">
<Grid x:Name="LayoutRoot">
<TextBlock
x:Name="label"
MaxHeight="407"
HorizontalAlignment="Left"
Margin="0,17,0,16"
VerticalAlignment="Center"
Text="{Binding Path=Title, ElementName=checklistItem}"/>
<CheckBox x:Name="checkbox" HorizontalAlignment="Right" VerticalAlignment="Center" Checked="checkbox_Checked" Unchecked="checkbox_Unchecked" />
</Grid>
</UserControl>
Custom UserControl
public partial class checklistItem : UserControl
{
public string Title
{
get { return (string)this.GetValue(TitleProperty); }
set { this.SetValue(TitleProperty, value); }
}
// Using a DependencyProperty as the backing store for MyProperty. This enables animation, styling, binding, etc...
public static readonly DependencyProperty TitleProperty = DependencyProperty.Register("Title", typeof(string), typeof(checklistItem), new PropertyMetadata(0));
public checklistItem()
{
InitializeComponent();
}
public bool isChecked()
{
if ((bool)checkbox.IsChecked) return true;
return false;
}
private void checkbox_Checked(object sender, RoutedEventArgs e)
{
//title.Foreground = Application.Current.Resources["PhoneAccentColor"];
label.Foreground = (SolidColorBrush)Application.Current.Resources["PhoneAccentBrush"];
}
private void checkbox_Unchecked(object sender, RoutedEventArgs e)
{
label.Foreground = new SolidColorBrush(Colors.White);
}
}
XAML in main page
<ListBox Height="479" ItemsSource="{Binding}" x:Name="CheckList">
<ListBox.ItemTemplate>
<DataTemplate>
<local:checklistItem Title="{Binding Title}" Width="397"/>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox
CS in main page
public ObservableCollection ListItems = new ObservableCollection();
ListItem item = new ListItem("Nowe",false);
ListItems.Add(item);
CheckList.DataContext = ListItems;
And finally List
public class ListItem
{
public string Title { get; set; }
public bool Checked { get; set; }
public ListItem(string title, bool isChecked=false)
{
Title = title;
Checked = isChecked;
}
}
I need someone who will look at code with fresh mind. Thank you.
UPDATE:
What's more for accessing controls in my CustomControl I use first anwser in that question .
Also I update my UserControl XAML.
UPDATE V2
When I'm using <TextBox Title="{Binding Title}" Width="397"/> insted of local:checklistItem it work just fine.
UPDATE V3
OK turns out that I didn't add proper DataContext to my UserControl so now checklistItem constructor looks like this
public checklistItem() {
InitializeComponent();
this.DataContext = this;
}
And controls are adding correctly but now binding for Title is not working. Because when I hardcode some title it working, binding not. But the same binding is working for default textbox.
I think you need to set the itemsSource rather than the DataContext. Setting DataContext on a Listbox will NOT generate the templated items
take out
ItemsSource="{Binding}"
from the Xaml
and change
CheckList.DataContext = ListItems;
to
CheckList.ItemsSource = ListItems;
I think that perhaps your binding in the XAML is incorrect. Your element name in the binding is LayoutRoot which is the name of your grid not your UserControl.
It is probably trying to find a property on the Grid called Title which it does not have.

Custom template injection in WPF

I have the following XAML:
<ListView Name="_listView">
<ListView.ItemTemplate>
<DataTemplate>
<Grid Name="_itemTemplate">
</Grid>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
What I would like to get is to create a property in my background code, that will take a custom user control and put it as a dynamic template. Something like this:
public UserControl ItemTemplate
{
set { _itemTemplate.Content = value; }
}
So then I can put my control in XAML of window and declare the item template like this:
<local:MyCustomControl ItemTemplate="local:ControlThatActsAsItemTemplate"/>
How to achieve somtehing like that?
So far I've found the following, simple solution.
In custom control XAML define the ListBox:
<ListBox Name="_listBox"/>
In code behind create a property:
public DataTemplate ItemTemplate
{
get { return _listBox.ItemTemplate; }
set { _listBox.ItemTemplate = value; }
}
In parent window or control set resources in XAML:
<Window.Resources>
<DataTemplate x:Key="CustomTemplate">
<TextBlock Text="{Binding Path=SomeProperty}"/>
</DataTemplate>
</Window.Resources>
Then declare the custom control:
<local:CustomControl ItemTemplate="{StaticResource CustomTemplate}"/>
Now you need to have an interface that exposes SomeProperty and data source comprising of such interface instances that you need to set to _listBox.ItemsSource. But this is another story.
Solution, that uses dependency property.
In custom UserControl declare dependency property that will inject item template to _listBox:
public static readonly DependencyProperty ItemTemplateProperty =
DependencyProperty.Register("ItemTemplate",
typeof(DataTemplate),
typeof(AutoCompleteSearchBox),
new PropertyMetadata(ItemTemplate_Changed));
public DataTemplate ItemTemplate
{
get { return (DataTemplate)GetValue(ItemTemplateProperty); }
set { SetValue(ItemTemplateProperty, value); }
}
private static void ItemTemplate_Changed(
DependencyObject d,
DependencyPropertyChangedEventArgs e)
{
var uc = (MyUserControl)d;
uc._listBox.ItemTemplate = (DataTemplate)e.NewValue;
}
Now you are free to set a value to that property in hosting window XAML:
<Window.Resources>
<Style TargetType="local:MyUserControl">
<Setter Property="ItemTemplate">
<Setter.Value>
<DataTemplate>
<StackPanel>
<TextBlock Text="{Binding Path=PropertyName}"/>
</StackPanel>
</DataTemplate>
</Setter.Value>
</Setter>
</Style>
</Window.Resources>
_listBox in your UserControl will gain a custom ItemTemplate that will respond to custom interface or class that you want to set as data source.

Categories