WPF TwoWay Binding not work with CustomControl in Template - c#

I have some problems with my Custom Control - Two way binding don't work when I use it in template.
So I have created template xaml
<ResourceDictionary
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:WpfApp1"
xmlns:controls="clr-namespace:GUIControls;assembly=GUIControls"
>
<ResourceDictionary
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:WpfApp1"
xmlns:controls="clr-namespace:GUIControls;assembly=GUIControls"
>
<ControlTemplate x:Key="YParamCombo" TargetType="ContentControl">
<controls:ParamCombo Header="MY CONTROL TEMPLATE"
Items="{Binding Items}"
PCValue="{Binding Codes[MY_CONTROL_TEMPLATE], Mode=TwoWay}"
Required="True"
MultiSelect="False"/>
</ControlTemplate>
<ControlTemplate x:Key="YComboBox" TargetType="ContentControl">
<ComboBox DisplayMemberPath="Name"
StaysOpenOnEdit="True"
ItemsSource="{Binding Items}"
SelectedValue="{Binding Codes[STANDARD_TEMPLATE], Mode=TwoWay}"
SelectedValuePath="Code"/>
</ControlTemplate>
MainWindow.xaml
<Window x:Class="WpfApp1.MainWindow"
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:local="clr-namespace:WpfApp1"
mc:Ignorable="d"
xmlns:controls="clr-namespace:GUIControls;assembly=GUIControls"
Title="MainWindow" Height="250" Width="525">
<Grid Margin="0,0,0,-1">
<Button Margin="62,162,299,4" Content="Show Codes-1" Click="Button_Click2"></Button>
<StackPanel HorizontalAlignment="Stretch" VerticalAlignment="Stretch">
<HeaderedContentControl Header="STANDARD CONTROL XAML" >
<ComboBox DisplayMemberPath="Name"
StaysOpenOnEdit="True"
ItemsSource="{Binding Items}"
SelectedValue="{Binding Codes[STANDARD_XAML]}"
SelectedValuePath="Code"/>
</HeaderedContentControl>
<HeaderedContentControl Header="STANDARD CONTROL TEMPLATE" >
<ContentControl Height="23" Template="{StaticResource YComboBox}"/>
</HeaderedContentControl>
<ContentControl Height="44" Template="{StaticResource YParamCombo}">
</ContentControl>
<controls:ParamCombo Header="MY CONTROL XAML"
Items="{Binding Items}"
PCValue="{Binding Codes[MYCONTROL_XAML], Mode=TwoWay}"
Required="True"
MultiSelect="False"/>
</StackPanel>
</Grid>
cs
using System.Linq;
using System.Windows;
namespace WpfApp1
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
DataContext = new WModel();
InitializeComponent();
}
private WModel vm { get { return (DataContext as WModel); } }
private void Button_Click2(object sender, RoutedEventArgs e)
{
MessageBox.Show(string.Join(";", vm.Codes._codes.Select(x => x.Key + "=" + x.Value).ToArray()));
}
}
}
using GUIControls;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
namespace WpfApp1
{
public class WModel
{
public WModel()
{
Codes = new CodesClass();
}
public string Caption { get; set; }
public ObservableCollection<Dict> Items
{
get
{
return new ObservableCollection<Dict>()
{
new Dict(){ Name = "Name1", Code = "Code1" } ,
new Dict(){ Name = "Name2", Code = "Code2" }
};
}
}
public CodesClass Codes { get; set; }
}
public class Dict : IDict
{
public string Name { get; set; }
public string Code { get; set; }
}
public class CodesClass
{
public Dictionary<string, object> _codes;
public CodesClass()
{
_codes = new Dictionary<string, object>();
}
public object this[string param]
{
get
{
if (_codes.ContainsKey(param))
return _codes[param];
else
return null;// "I have no " + param;
}
set
{
_codes[param] = value;
}
}
}
}
When I run app and select all 4 comboboxes and Press button, I can see that twoway binding in one combobox(Custom Control declared in template ) do not work
---------------------------
---------------------------
STANDARD_XAML=Code2;STANDARD_TEMPLATE=Code2;MYCONTROL_XAML=Code2
---------------------------
ОК
---------------------------
Some code from control
public static readonly DependencyProperty PCValueProperty =
DependencyProperty.Register("PCValue", typeof(string), typeof(ParamCombo),
new FrameworkPropertyMetadata(null, FrameworkPropertyMetadataOptions.BindsTwoWayByDefault, new PropertyChangedCallback(OnPCValuePropertyChanged)));
//new PropertyMetadata(new PropertyChangedCallback(OnValuePropertyChanged)));, new PropertyChangedCallback(OnPCValuePropertyChanged))
#endregion
private static void OnPCValuePropertyChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
{
ParamCombo paramCombo = (ParamCombo)sender;
paramCombo.UpdateSelected((e.NewValue == null) ? "" : e.NewValue.ToString());
}
Thanks for your help!

I have had problems with twoway binding in a combo in a customcontrol template and the solution was to override OnApplyTemplate in the custom control, using Template.FindName to get the combo, get the DataContex object of the combo and raise PropertyChanged in the DataContext object for the bound property. My problem was to update the combo when the window was loaded.

Related

WPF ItemsControl can't bind Command

I'm a newbie in wpf and i know that this question has been asked other times, and i tried to implement some solutions that i found. But it's not working. I'm doing something wrong but i can't see what it is.
I've created a new simple application to test this problem.
namespace WpfApp3
{
public class MyElement
{
public string Text { get; set; }
public MyElement(string t)
{
Text = t;
}
}
public class MyCommand : ICommand
{
public bool CanExecute(object parameter)
{
return true;
}
public event EventHandler CanExecuteChanged;
public void Execute(object parameter)
{
_handler(parameter);
}
private Action<object> _handler;
public MyCommand(Action<object> handler) { _handler = handler; }
}
public class MyItemsControlViewModel
{
ObservableCollection<MyElement> _items;
public ObservableCollection<MyElement> MyElementItems { get { return _items; } set { _items = value; RaisePropertyChanged("MyElementItems"); } }
ObservableCollection<MyElement> _temporayList;
private ICommand _itemClicked;
public ICommand ItemClicked { get { return _itemClicked; } }
public MyItemsControlViewModel()
{
_items = new ObservableCollection<MyElement>();
_temporayList = new ObservableCollection<MyElement>();
_itemClicked = new MyCommand(OnItemSelected);
AddItem("Element 1");
AddItem("Element 2");
AddItem("Element 3");
UpdateList();
}
public void UpdateList()
{
MyElementItems = _temporayList;
}
public void AddItem(string t)
{
MyElement item = new MyElement(t);
_temporayList.Add(item);
}
public void OnItemSelected(object param)
{
Debug.WriteLine("Executed!");
}
public event PropertyChangedEventHandler PropertyChanged;
private void RaisePropertyChanged(string propertyName)
{
if (this.PropertyChanged != null)
this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
XAML
<UserControl x:Class="WpfApp3.MyUserControl"
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:WpfApp3"
mc:Ignorable="d"
d:DesignHeight="1080" d:DesignWidth="570"
x:Name="myCustomControl">
<Grid >
<Button x:Name="btnOutsideItemsControl" Width="100" Height="100 " VerticalAlignment="Top" Command="{Binding ItemClicked}" />
<ItemsControl
x:Name="listItems"
ScrollViewer.PanningMode="None"
IsEnabled="False"
Background = "Transparent"
HorizontalAlignment="Center"
ItemsSource="{Binding MyElementItems}" Margin="0,152,0,0" Width="549">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel HorizontalAlignment="Left" Margin="50,0,0,0"
Background="Transparent" Orientation="Vertical"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Button
Content="{Binding Text}"
Command="{Binding ElementName=listItems, Path=DataContext.ItemClicked}"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Grid>
</UserControl>
The component is used in MainWindow.xaml.
namespace WpfApp3
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
private MyItemsControlViewModel _myViewModel;
public MyItemsControlViewModel MyViewModel { get { return _myViewModel; } }
public MainWindow()
{
_myViewModel = new MyItemsControlViewModel();
InitializeComponent();
myCustomControl.DataContext = MyViewModel;
}
}
}
XAML
<Window x:Class="WpfApp3.MainWindow"
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:local="clr-namespace:WpfApp3"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<Grid>
<local:MyUserControl x:Name="myCustomControl"/>
</Grid>
</Window>
When i run the application i can see correctly the list of 3 items with the correct text.
But if i click on one of the button of the list i can't see the output of Debug.WriteLine("Executed!");
But if i click on the button btnOutsideItemsControl that is outside the ItemsControl, it works. I can see the output of Debug.WriteLine("Executed!");
So i think that also the definition of the command is correct.
To bind correctly the Command property of Button inside the ItemsControl i try this
<Button Command="{Binding ElementName=listItems, Path=DataContext.ItemClicked}">
And also this
<Button Command="{Binding DataContext.ItemClicked, RelativeSource={RelativeSource Mode=FindAncestor,AncestorType=ItemsControl}}">
But it not works.
Please help!
You're gonna kick yourself once I tell you.
Your problem is that you set IsEnabled="False" on your ItemsControl. Remove it and all will be well with the universe.

Changing the ContentControl binding results in a null DataContext

Perhaps one of you could enlighten me on the following problem.
I'm using a list box as a way to navigate between 2 viewmodels.
<Window x:Class="MyWPFApp.ShellView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:cal="http://www.caliburnproject.org">
<Grid Background="White">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="2*"/>
<ColumnDefinition Width="8*"/>
</Grid.ColumnDefinitions>
<ListBox Grid.Column="0" cal:Message.Attach="[Event SelectionChanged]
= [Action onViewSelectionChange($this.SelectedItem.Text)]">
<TextBlock Text="FirstView"/>
<TextBlock Text="SecondView"/>
</ListBox>
<ContentControl Grid.Column="1" Content="{Binding MyViewModel}"/>
</Grid>
namespace MyWPFApp
{
public class ShellViewModel : Caliburn.Micro.PropertyChangedBase, IShell
{
private object _vm;
public object MyViewModel
{
get { return _vm; }
set { _vm = value; NotifyOfPropertyChange(() => MyViewModel); }
}
public FirstViewModel vm1;
public SecondViewModel vm2;
public ShellViewModel()
{
vm1 = new FirstViewModel();
vm2 = new SecondViewModel();
MyViewModel = vm1;
}
public void onViewSelectionChange(string str)
{
switch (str)
{
case "FirstView":
MyViewModel = vm1;
break;
case "SecondView":
MyViewModel = vm2;
break;
default:
break;
}
}
}
}
I've create 2 datatemplates to associate the viewmodel with its view
<Application x:Class="MyWPFApp.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-MyWPFApp">
<Application.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary>
<local:AppBootstrapper x:Key="bootstrapper" />
</ResourceDictionary>
</ResourceDictionary.MergedDictionaries>
<DataTemplate DataType="{x:Type local:FirstViewModel}">
<Grid>
<local:FirstView></local:FirstView>
</Grid>
</DataTemplate>
<DataTemplate DataType="{x:Type local:SecondViewModel}">
<Grid>
<local:SecondView></local:SecondView>
</Grid>
</DataTemplate>
</ResourceDictionary>
</Application.Resources>
</Application>
FirstView actually has controls which have bindings to properties in FirstViewModel
<UserControl x:Class="MyWPFApp.FirstView"
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:MyWPFApp"
xmlns:cal="http://www.caliburnproject.org"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300">
<Grid Background="Aquamarine">
<ListBox SelectedIndex="{Binding SelectedIndex}"
SelectedItem="{Binding SelectedItem}"
cal:Message.Attach="[Event SelectionChanged]=[Action OnLBSelectionChanged($source)]">
<ListBoxItem>apple</ListBoxItem>
<ListBoxItem>orange</ListBoxItem>
<ListBoxItem>pear</ListBoxItem>
</ListBox>
</Grid>
using Caliburn.Micro;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace MyWPFApp
{
public class FirstViewModel : PropertyChangedBase
{
private int _selectedindex;
public int SelectedIndex
{
get { return _selectedindex; }
set { _selectedindex = value; NotifyOfPropertyChange(() => SelectedIndex); }
}
private string _selecteditem;
public string SelectedItem
{
get { return _selecteditem; }
set { _selecteditem = value; NotifyOfPropertyChange(() => SelectedItem); }
}
public FirstViewModel()
{
}
public void OnLBSelectionChanged(System.Windows.Controls.ListBox lb)
{
if(lb.DataContext == null)
{
Debug.WriteLine("The DataContext is null");
}
}
}
}
The SecondView contains no controls.
When the ShellView list box selection changes from FirstView to SecondView, I've noticed that the FirstView ListBox DataContext becomes null.
public void OnLBSelectionChanged(System.Windows.Controls.ListBox lb)
{
if(lb.DataContext == null)
{
Debug.WriteLine("The DataContext is null");
}
}
I would prefer not to have to handle selection changes looking for a null DataContext. Instead, I'd like to know what's actually happening.
Thanks.
The reason why this is happening is that the DataContext for FirstView is implicitly wired up by WPF when the ContentControl's Content property is set to the FirstViewModel via data binding.
Once the binding changes to be SecondViewModel the FirstViewModel goes out of scope and thus WPF cleans up after itself, removing FirstViewModel, FirstView, and its implicit DataContext binding.
I hope this helps.

WPF/C# Assigning a ViewModel to a custom control from parent view

I'm very new to C# and WPF, and I'm struggling a bit to get data where I need it.
I have one master set of data, which needs to be shared with various user controls, each of which have their own ViewModel. The problem is that I don't seem to be able to assign a ViewModel to a control from the parent XAML and then access that ViewModel from within the custom control's XAML.
I bind the control to a Viewmodel, but then the datacontext within the control doesn't allow me to access that model within the xaml, or I can set the datacontext in the user control so I can access its viewmodel, but then I can't bind to the viewmodel in xaml (because the binding is looking in the local datacontext, not the parent).
I may be going about this all wrong, most examples I've seen seem to instantiate a ViewModel in the custom control xaml, but then I don't see how you get that ViewModel to reference the correct DataModel (or specific part of the datamodel).
The following hopefully explains what I am trying to do.
Firstly I have my data model, in DataModel.cs
using System;
using System.Collections.Generic;
namespace BindingTest1
{
public class DataModel
{
private List<string>[] _dataLists;
public List<string>[] DataLists
{
get { return _dataLists; }
}
public DataModel()
{
List<string> list0 = new List<string> { "One", "Two", "Three" };
List<string> list1 = new List<string> { "Alpha", "Beta", "Gamma" };
_dataLists = new List<String>[] { list0, list1 };
}
}
}
In MainViewModel.cs
namespace BindingTest1
{
class MainViewModel
{
private MyViewModel _myFirstViewModel;
public MyViewModel MyFirstViewModel
{
get { return _myFirstViewModel; }
}
private MyViewModel _mySecondViewModel;
public MyViewModel MySecondModel
{
get { return _mySecondViewModel; }
}
private DataModel _dataModel;
public DataModel DataModel
{
get { return _dataModel; }
}
public MainViewModel()
{
_dataModel = new DataModel();
_myFirstViewModel = new MyViewModel(_dataModel.DataLists[0]);
_mySecondViewModel = new MyViewModel(_dataModel.DataLists[0]);
}
}
}
MainWindow.xaml
<Window x:Class="BindingTest1.MainWindow"
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:local="clr-namespace:BindingTest1"
mc:Ignorable="d"
xmlns:diag="clr-namespace:System.Diagnostics;assembly=WindowsBase"
Title="MainWindow" Height="350" Width="525">
<Window.DataContext>
<local:MainViewModel/>
</Window.DataContext>
<Grid>
<StackPanel HorizontalAlignment="Stretch" Height="100" VerticalAlignment="Top" Orientation="Horizontal">
<!-- These were just to check the data was being set up properly -->
<ListBox x:Name="listBox1" HorizontalAlignment="Left" Height="100" VerticalAlignment="Top" Width="100" ItemsSource="{Binding DataModel.DataLists[0]}"/>
<ListBox x:Name="listBox2" HorizontalAlignment="Left" Height="100" VerticalAlignment="Top" Width="100" ItemsSource="{Binding DataModel.DataLists[1]}"/>
<!-- this is what I want to be able to do -->
<local:MyView ViewModel="{Binding MyFirstViewModel}"/>
<local:MyView ViewModel="{Binding MySecondViewModel}"/>
</StackPanel>
</Grid>
</Window>
(Codebehind is default)
In MyViewModel.cs
using System;
using System.Collections.Generic;
namespace BindingTest1
{
public class MyViewModel
{
private List<string> _dataList;
public List<string> DataList
{
get { return _dataList; }
}
public MyViewModel(List<string> list)
{
_dataList = new List<String>(list);
_dataList.Add("Some Local Processing");
}
}
}
MyView.xaml
<UserControl x:Class="BindingTest1.MyView"
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:BindingTest1"
mc:Ignorable="d"
d:DesignHeight="100" d:DesignWidth="100">
<Grid>
<ListBox x:Name="listBox" HorizontalAlignment="Left" Height="100" VerticalAlignment="Top" Width="100" ItemsSource="{Binding ViewModel.DataList}"/>
</Grid>
</UserControl>
Codebehind
using System.Windows;
using System.Windows.Controls;
namespace BindingTest1
{
/// <summary>
/// Interaction logic for MyView.xaml
/// </summary>
public partial class MyView : UserControl
{
public MyViewModel ViewModel
{
get { return (MyViewModel)GetValue(ViewModelProperty); }
set { SetValue(ViewModelProperty, value); }
}
public static readonly DependencyProperty ViewModelProperty = DependencyProperty.Register("ViewModel", typeof(MyViewModel), typeof(MyView),
new FrameworkPropertyMetadata(null, FrameworkPropertyMetadataOptions.None, new PropertyChangedCallback(OnViewModelChanged)));
public MyView()
{
InitializeComponent();
}
private static void OnViewModelChanged(DependencyObject obj, DependencyPropertyChangedEventArgs e)
{
// Just making sure the right thing is being received
List<string> dataList = (e.NewValue as MyViewModel).DataList;
foreach(string line in dataList)
{
System.Console.WriteLine(line);
}
}
}
}
I don't think you need a dependency property here.
Try this.
<local:MyView DataContext="{Binding MyFirstViewModel}"/>
<local:MyView DataContext="{Binding MySecondViewModel}"/>
and bind the DataList to ItemsSource in the MyView XAML.
As you assigned MyFirstViewModel to the DataContext of MyView, bindings inside will look in MyFirstViewModel for the ItemsSource.
Here's how you ought to do this. Your view doesn't need a ViewModel property. It should bind to properties of its DataContext, which will be the viewmodel.
view:
ItemsSource="{Binding DataList}"
Window:
<Window.Resources>
<DataTemplate DataType="{x:Type local:MyViewModel}">
<local:MyView
/>
</DataTemplate>
</Window.Resources>
<Grid>
<StackPanel HorizontalAlignment="Stretch" Height="100" VerticalAlignment="Top" Orientation="Horizontal">
<!-- ... -->
<ContentControl Content="{Binding MyFirstViewModel}"/>
<ContentControl Content="{Binding MySecondViewModel}"/>
</StackPanel>

How to set dependency property of ItemsControl.ItemTemplate and handle its events

I have a main control (MainWindow.xaml) and an user control (ItemView.xaml). MainWindow contains an ItemsControl for all the ItemView-s and a simple button to add an item. All logic is (should be?) inside two corresponding viewmodels (MainWindowViewModel and ItemViewModel). Below is my code (made it as short as possible), but I have two problems with it:
When a new item is added it is correctly displayed but the exception is raised (Cannot create default converter to perform 'two-way' conversions between types 'WpfApplication1.ItemView' and 'WpfApplication1.ItemViewModel'.).
The OnDelete event handler in MainWindowViewModel is never raised? Edit: actually the ViewModel property inside BtnDeleteClick is null so yeah... of course.
Btw - I use Fody PropertyChanged.
MainWindow.xaml:
<Window x:Class="WpfApplication1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:wpfApplication1="clr-namespace:WpfApplication1"
Title="MainWindow" Height="350" Width="525"
DataContext="{Binding RelativeSource={RelativeSource Self}}">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"></RowDefinition>
<RowDefinition></RowDefinition>
</Grid.RowDefinitions>
<Button Grid.Row="0" Width="100" Height="35" Content="Add" HorizontalAlignment="Left" Margin="10" Click="BtnAddClick"></Button>
<Border Grid.Row="1" MinHeight="50">
<ItemsControl ItemsSource="{Binding ViewModel.Items}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<wpfApplication1:ItemView ViewModel="{Binding ., PresentationTraceSources.TraceLevel=High, Mode=TwoWay}"></wpfApplication1:ItemView>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Border>
</Grid>
</Window>
MainWindow.xaml.cs:
[ImplementPropertyChanged]
public partial class MainWindow
{
public MainWindowViewModel ViewModel { get; set; }
public MainWindow()
{
InitializeComponent();
ViewModel = new MainWindowViewModel();
}
private void BtnAddClick(object sender, RoutedEventArgs e)
{
ViewModel.Add();
}
}
MainWindowViewModel.cs:
[ImplementPropertyChanged]
public class MainWindowViewModel
{
public ObservableCollection<ItemViewModel> Items { get; set; }
public MainWindowViewModel()
{
Items = new ObservableCollection<ItemViewModel>();
}
public void Add()
{
var item = new ItemViewModel();
item.OnDelete += (sender, args) =>
{
Debug.WriteLine("-- WAITING FOR THIS TO HAPPEN --");
Items.Remove(item);
};
Items.Add(item);
}
}
ItemViewModel.xaml:
<UserControl x:Class="WpfApplication1.ItemView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
DataContext="{Binding RelativeSource={RelativeSource Self}}">
<Grid>
<Button Width="100" Height="35" Content="Delete" Click="BtnDeleteClick"></Button>
</Grid>
</UserControl>
ItemView.xaml.cs:
[ImplementPropertyChanged]
public partial class ItemView
{
public static readonly DependencyProperty ViewModelProperty = DependencyProperty.Register
(
"ViewModel", typeof(ItemViewModel), typeof(ItemView), new UIPropertyMetadata(null)
);
public ItemViewModel ViewModel
{
get { return (ItemViewModel)GetValue(ViewModelProperty); }
set { SetValue(ViewModelProperty, value); }
}
public ItemView()
{
InitializeComponent();
}
private void BtnDeleteClick(object sender, RoutedEventArgs e)
{
ViewModel.Delete();
}
}
And ItemViewModel.cs:
[ImplementPropertyChanged]
public class ItemViewModel
{
public event EventHandler OnDelete;
public void Delete()
{
var handler = OnDelete;
if (handler != null)
{
handler(this, new EventArgs());
}
}
}
You should not set
DataContext="{Binding RelativeSource={RelativeSource Self}}"
in the XAML of your ItemView. It effectively breaks the ViewModel="{Binding .}" binding in MainWindow.xaml, because the DataContext is no longer an ItemsViewModel, but an ItemsView.
As a rule, you should never explicitly set the DataContext of a UserControl, because all "external" bindings would then require an explicit Source or RelativeSource value.
That said, you're doing all this way too complicated. Instead of having a button click handler in your ItemsView, you could simply have a view model with a delete command, and bind the Button's Command property to this command.
It may look like this:
public class ItemViewModel
{
public string Name { get; set; }
public ICommand Delete { get; set; }
}
public class MainViewModel
{
public MainViewModel()
{
Items = new ObservableCollection<ItemViewModel>();
}
public ObservableCollection<ItemViewModel> Items { get; private set; }
public void AddItem(string name)
{
Items.Add(new ItemViewModel
{
Name = name,
Delete = new DelegateCommand(p => Items.Remove(p as ItemViewModel))
});
}
}
and would be used like this:
<UserControl x:Class="WpfApplication1.ItemView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<Grid>
<Button Content="Delete"
Command="{Binding Delete}"
CommandParameter="{Binding}"/>
</Grid>
</UserControl>

Items Template code-behind

Is there a way to access the containers generated for an itemscontrol's items?
For example,
Given an ItemsControl with an ItemTemplate as shown below, access the actual TextBlock within the DataTemplate generated for each item in the control. (not the object, but its associated textblock).
Views/MainPage.xaml:
<phone:PhoneApplicationPage
x:Class="PhoneApp1.MainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:phone="clr-namespace:Microsoft.Phone.Controls;assembly=Microsoft.Phone"
xmlns:shell="clr-namespace:Microsoft.Phone.Shell;assembly=Microsoft.Phone"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:vm="clr-namespace:PhoneApp1.ViewModels"
xmlns:toolkit="clr-namespace:Microsoft.Phone.Controls;assembly=Microsoft.Phone.Controls. Toolkit"
mc:Ignorable="d"
FontFamily="{StaticResource PhoneFontFamilyNormal}"
FontSize="{StaticResource PhoneFontSizeNormal}"
Foreground="{StaticResource PhoneForegroundBrush}"
SupportedOrientations="Portrait" Orientation="Portrait"
shell:SystemTray.IsVisible="True">
<!--LayoutRoot is the root grid where all page content is placed-->
<phone:Pivot>
<phone:Pivot.Resources>
<vm:PersonViewModel x:Key="ViewModel"/>
</phone:Pivot.Resources>
<phone:PivotItem>
<phone:PivotItem.Header>
<TextBlock Text="Pivot"/>
</phone:PivotItem.Header>
<ItemsControl x:Name="People" DataContext="{StaticResource ViewModel}" ItemsSource="{Binding People}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<TextBlock toolkit:SlideInEffect.LineIndex="{Binding PersonLineIndex}" Text="{Binding Name}"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</phone:PivotItem>
<phone:PivotItem>
<phone:PivotItem.Header>
<TextBlock Text="Empty Pivot"/>
</phone:PivotItem.Header>
</phone:PivotItem>
</phone:Pivot>
</phone:PhoneApplicationPage>
Views/MainPage.xaml.cs
using Microsoft.Phone.Controls;
namespace PhoneApp1
{
public partial class MainPage : PhoneApplicationPage
{
// Constructor
public MainPage()
{
InitializeComponent();
}
}
}
ViewModels/PersonViewModel.cs
using PhoneApp1.Models;
using System.Collections.Generic;
using System.Collections.ObjectModel;
namespace PhoneApp1.ViewModels
{
public class PersonViewModel
{
public PersonViewModel()
{
People = new ObservableCollection<Person>
{
new Person { Name = "Joe" },
new Person { Name = "Jack" },
new Person { Name = "James" },
new Person { Name = "John" }
};
PersonLineIndex = new List<int>();
for (int i = 0; i < People.Count; i++)
{
PersonLineIndex.Add(i);
}
}
public ObservableCollection<Person> People { get; set; }
public List<int> PersonLineIndex { get; set; }
}
}
Models/Person.cs:
namespace PhoneApp1.Models
{
public class Person
{
public string Name { get; set; }
}
}
Why would I need this access? For example:
Try to set a different line index for each textblock. Without adding a "LineIndex" property to your Person (as that would violate MVVM).
I figured it out. You would access such ItemsControl's ItemContainerGenerator
public partial class MainPage : PhoneApplicationPage
{
// Constructor
public MainPage()
{
InitializeComponent();
People.Loaded += People_Loaded;
}
void People_Loaded(object sender, System.Windows.RoutedEventArgs e)
{
for (int i = 0; i < People.Items.Count; i++)
{
var container = People.ItemContainerGenerator.ContainerFromIndex(i);
container.SetValue(SlideInEffect.LineIndexProperty, i);
}
}
}

Categories

Resources