I'm new to WPF and I'm try to bind a dependacy property.
I want that the the text I write on the WPFCtrl:FilterTextBox will be displaied in the TextBlock
here is my XAML
xmlns:WPFCtrl="clr-namespace:WPFControls"
xmlns:local="clr-namespace:WpfApplication9"
Title="MainWindow" Height="350" Width="525">
<Window.Resources>
<local:Person x:Key="myDataSource" />
</Window.Resources>
<Grid>
<StackPanel>
<StackPanel.DataContext>
<Binding Source="{StaticResource myDataSource}"/>
</StackPanel.DataContext>
<WPFCtrl:FilterTextBox Text="{Binding Path=Name, UpdateSourceTrigger=PropertyChanged }"/>
<TextBlock Width="55" Height="25" Text="{Binding Path=Name, UpdateSourceTrigger=PropertyChanged}"/>
</StackPanel>
</Grid>
here the Person Class
namespace WpfApplication9
{
public class Person : INotifyPropertyChanged
{
private string name = "";
// Declare the event
public event PropertyChangedEventHandler PropertyChanged;
public Person()
{
}
public Person(string value)
{
this.name = value;
}
public string Name
{
get { return name; }
set
{
name = value;
// Call OnPropertyChanged whenever the property is updated
OnPropertyChanged("Name");
}
}
// Create the OnPropertyChanged method to raise the event
protected void OnPropertyChanged(string name)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(name));
}
}
}
}
and the FilterTextBox Text Property
public static readonly DependencyProperty TextProperty = DependencyProperty.Register("Text", typeof(string), typeof(FilterTextBox), new PropertyMetadata());
public string Text
{
//get { return _tbFilterTextBox.Text == null ? null : _tbFilterTextBox.Text.TrimEnd(); }
get { return (string)GetValue(TextProperty); }
set { SetValue(TextProperty, value); }
//set { _tbFilterTextBox.Text = value; }
}
The problem is that it does not enter in OnPropertyChanged()
What am I doing wrong?
does this "FilterTextBox" Control update the DP everytime text is inserted?
i guess the FilterTextBox has a ControlTemplate with a regular TextBox inside.
something like
<ControlTemplate TargetType="{x:Type FilterTextBox}">
<TextBox Name="PART_FilterTextBoxInputField" Text="{TemplateBinding Text}"/>
</ControlTemplate>
you need to set the binding where the internal textbox binds to your Text-Dependcy Property to use UpdateSourceTrigger=PropertyChanged too. otherwise the binding will only update when the textbox loses focus.
The problem is that the TextProperty in FilterTextBox doesn't bind TwoWay by default.
Either set the BindingMode to TwoWay
<WPFCtrl:FilterTextBox Text="{Binding Path=Name,
Mode=TwoWay,
UpdateSourceTrigger=PropertyChanged }"/>
Or change the metadata for the DependencyProperty Text so it binds twoway by default
public static readonly DependencyProperty TextProperty =
DependencyProperty.Register("Text",
typeof(string),
typeof(FilterTextBox),
new FrameworkPropertyMetadata(null,
FrameworkPropertyMetadataOptions.BindsTwoWayByDefault));
Related
I am a newbie in WPF, I have a problem concern DataContext inheritance from the MainWindow to a UserControl,
which will be attached as a Tabpage to the MainWindow's Tabcontrol.
My code snippets are as follows:
UserControlModel.cs
public class UserControlModel : INotifyPropertyChanged
{
private string _name;
public string Name
{
get { return _name; }
set
{
if (_name != value)
{
_name = value;
OnPropertyChanged("Name");
}
}
}
// Create the OnPropertyChanged method to raise the event
protected void OnPropertyChanged(string name)
{
var handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(name));
}
}
#region INotifyPropertyChanged Members
public event PropertyChangedEventHandler PropertyChanged;
#endregion
}
ViewModelLocator.cs
public class ViewModelLocator
{
private UserControlModel UserControlModel { get; set; }
public ObservableCollection<UserControlModel> Users { get; set; }
public ViewModelLocator()
{
Users = new ObservableCollection<UserControlModel>
{
new UserControlModel { Name = "Albert" },
new UserControlModel { Name = "Brian" }
};
}
}
MainWindow.xaml
<Window.Resources>
<local:ViewModelLocator x:Key="VMLocator" />
</Window.Resources>
<Grid HorizontalAlignment="Left" Height="330" VerticalAlignment="Top" Width="592">
<Grid HorizontalAlignment="Left" Height="45" Margin="0,330,-1,-45" VerticalAlignment="Top" Width="593">
<Button Content="Button" HorizontalAlignment="Left" Margin="490,5,0,0" VerticalAlignment="Top" Width="75" Click="Button_Click"/>
</Grid>
<TabControl HorizontalAlignment="Left" Height="330" VerticalAlignment="Top" Width="592" >
<TabItem x:Name="UserControlTabItem" Header="User Control">
<Grid x:Name="UserControlTabpage" Background="#FFE5E5E5">
<local:UserControl VerticalAlignment="Top" DataContext="{Binding Users, Source={StaticResource VMLocator}}" />
</Grid>
</TabItem>
</TabControl>
</Grid>
I create an instance of ViewModelLocator and bind Users instance to the UserControl in MainWindow.xaml.
MainWindow.xaml.cs
public MainWindow()
{
InitializeComponent();
}
UserControl.xaml
<Grid>
<ListBox x:Name="lbUsers" DisplayMemberPath="???" HorizontalAlignment="Left" Height="250" Margin="30,27,0,0" VerticalAlignment="Top" Width="378"/>
</Grid>
UserControl.xaml.cs
private ObservableCollection<UserControlModel> _users;
public UserControl()
{
InitializeComponent();
_users = ??? How to reference the Users instance created in MainWindow ???
lbUsers.ItemsSource = _users;
}
Actually, I want to show the Name property of UserControlModel in the ListBox. If I am right, UserControl instance is
inherited with a Users instance as the DataContext from MainWindow. How can I reference the Users instance in the code-behind
of UserControl.xaml.cs? I have checked that DataContext in UserControl constructor is null! How come? What is the
correct way/place to test the DataContext in the code-behind?
Also, how to set DisplayMemberPath attribute of the ListBox in UserControl.xaml. Many thanks.
I think you can set or inherit DataContext in XAML of user control like this
UserControl.xaml
<dialogs:Usercontrol DataContext="{Binding RelativeSource={RelativeSource AncestorType=Window}, Path=DataContext}" />
You need to bind the ItemsSource property of the ListBox to the source collection. Since the DataContext of the UserControl is a ObservableCollection<UserControlModel>, you can bind to it directly:
<ListBox x:Name="lbUsers" ItemsSource="{Binding}" DisplayMemberPath="Name" ... />
Also make sure that you don't explicitly set the DataContext of the UserControl anywhere else in your code.
Although you should be able to reference the DataContext once the UserControl has been loaded:
public UserControl()
{
InitializeComponent();
this.Loaded += (s, e) =>
{
_users = DataContext as ObservableCollection<UserControlModel>;
};
}
...there is no need to set the ItemsSource property of the ListBox in the code-behind. You should do this by creating a binding in the XAML.
This is my combobox usercontrol:
<UserControl x:Class="Hexa.Screens.UsrColorPicker"
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:Hexa.Screens"
xmlns:sys="clr-namespace:System;assembly=mscorlib" Height="40" Width="200" Name="uccolorpicker"
mc:Ignorable="d">
<UserControl.Resources>
<ResourceDictionary>
<ObjectDataProvider MethodName="GetType" ObjectType="{x:Type sys:Type}" x:Key="colorsTypeOdp">
<ObjectDataProvider.MethodParameters>
<sys:String>System.Windows.Media.Colors, PresentationCore, Version=3.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35</sys:String>
</ObjectDataProvider.MethodParameters>
</ObjectDataProvider>
<ObjectDataProvider ObjectInstance="{StaticResource colorsTypeOdp}" MethodName="GetProperties" x:Key="colorPropertiesOdp"/>
</ResourceDictionary>
</UserControl.Resources>
<ComboBox Name="superCombo" ItemsSource="{Binding Source={StaticResource colorPropertiesOdp}}" SelectedValuePath="Name" SelectedValue="{Binding ElementName=uccolorpicker, Path=SelectedColor}" Text="{Binding ElementName=uccolorpicker,Path=Text}" SelectedItem="{Binding ElementName=uccolorpicker, Path=SelectedItem}" SelectedIndex="{Binding ElementName=uccolorpicker, Path=SelectedIndex}" SelectionChanged="superCombo_SelectionChanged" HorizontalContentAlignment="Stretch" >
<ComboBox.ItemTemplate>
<DataTemplate >
<WrapPanel Orientation="Horizontal" Background="Transparent">
<TextBlock Width="20" Height="20" Margin="5" Background="{Binding Name}" />
<TextBlock Text="{Binding Name}" HorizontalAlignment="Center" VerticalAlignment="Center" FontSize="14" FontStyle="Italic" FontWeight="Bold" FontFamily="Palatino Linotype" />
</WrapPanel>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
This is the code behind:
namespace Hexa.Screens
{
/// <summary>
/// Interaction logic for UsrColorPicker.xaml
/// </summary>
public partial class UsrColorPicker : UserControl
{
public UsrColorPicker()
{
InitializeComponent();
}
public Brush SelectedColor
{
get { return (Brush)GetValue(SelectedColorProperty); }
set { SetValue(SelectedColorProperty, value); }
}
public int SelectedIndex
{
get { return (int)GetValue(SelectedIndexProperty); }
set { SetValue(SelectedIndexProperty, value); }
}
public int SelectedItem
{
get { return (int)GetValue(SelectedItemProperty); }
set { SetValue(SelectedItemProperty, value); }
}
public string Text
{
get { return (string)GetValue(SelectedTextProperty); }
set { SetValue(SelectedTextProperty, value); }
}
// Using a DependencyProperty as the backing store for SelectedColor. This enables animation, styling, binding, etc...
public static readonly DependencyProperty SelectedColorProperty =
DependencyProperty.Register("SelectedColor", typeof(Brush), typeof(UsrColorPicker), new UIPropertyMetadata(null));
public static readonly DependencyProperty SelectedIndexProperty =
DependencyProperty.Register("SelectedIndex", typeof(int), typeof(UsrColorPicker), new UIPropertyMetadata(null));
public static readonly DependencyProperty SelectedItemProperty =
DependencyProperty.Register("SelectedItem", typeof(int), typeof(UsrColorPicker), new UIPropertyMetadata(null));
public static readonly DependencyProperty SelectedTextProperty =
DependencyProperty.Register("Text", typeof(int), typeof(UsrColorPicker), new UIPropertyMetadata(null));
public static readonly RoutedEvent SettingConfirmedEvent =
EventManager.RegisterRoutedEvent("SettingConfirmedEvent", RoutingStrategy.Bubble,
typeof(RoutedEventHandler), typeof(UsrColorPicker));
public event RoutedEventHandler SettingConfirmed
{
add { AddHandler(SettingConfirmedEvent, value); }
remove { RemoveHandler(SettingConfirmedEvent, value); }
}
private void superCombo_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
RaiseEvent(new RoutedEventArgs(UsrColorPicker.SettingConfirmedEvent));
}
}
}
I am trying to set its SelectedItem,SelectedIndex thru xaml binding in my container's XAML as under:-
<local:UsrColorPicker x:Name="cmbItem_Group_back_color" HorizontalAlignment="Center" Width="205" Height="22" SettingConfirmed="cmbItem_Group_back_color_SettingConfirmed" SelectedColor ="{Binding Path=CurrentRec.Primary_Tone,Mode=TwoWay}" Canvas.Left="97" Canvas.Top="92" />
The code behind is as under:-
form_load()
{
this.DataContext = DataContract_ButtonSettings;
}
But the selecteditem's text is not showing on the combobox as it should.
I found the solution.It was a careless mistake..
The bug was nowhere in the code posted..Actually,i was using the usercontrol's selectedcolor property to bind to the backcolor property of an element in the view.The viewmodel updates the SelectedColor property of the ColorCombobox.The viewmodel was being updated from many places in the code behind.And at one place,the viewmodel's SelectedColor property was being set with the HEX equivalent of the known Color of System.Windows.Media.Colors Known color,and when the view model wud try to bind to that Hex element to the ComboBox,it did not find any matching entry for that Hex value in the dropdown list,and hence,though the background color of the control was being effected,the combobox text showed blank.:-).
anyways,solved it...thanks for your time Ed. :-).
I have a strange issue when using DataBinding on a custom UserControl.
My UserControl "UserControl1" has a dependency property LabelText which sets the content of a label within my UserControl1. Furthermore, it has a button that binds the command "MyCommand". This command just shows a message box and is implemented in UserControl1ViewModel.
When I using UserControl1 in my MainWindow with also has its view model (MainWindowViewModel), I would like to set the UserControl's LabelText property in the MainWindow.xaml using a Binding to LabelTextFromMainWindow, but when I do it I have a problem that it uses the wrong DataContext unless you specify it explicitly.
This my code:
public partial class MainWindow : Window
{
private MainWindowViewModel vm;
public MainWindow()
{
InitializeComponent();
DataContext = vm = new MainWindowViewModel();
vm.LabelTextFromMainWindow = "Hallo";
}
}
class MainWindowViewModel : System.ComponentModel.INotifyPropertyChanged
{
#region INotifyPropertyChanged Members
public event System.ComponentModel.PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
PropertyChanged(this,
new System.ComponentModel.PropertyChangedEventArgs(propertyName));
}
#endregion
private string myLabel;
public string LabelTextFromMainWindow
{
get { return myLabel; }
set
{
myLabel = value;
OnPropertyChanged("MyLabel");
}
}
}
/////////
<UserControl x:Class="WpfApplication1.UserControl1"
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="224" d:DesignWidth="300">
<Grid>
<Button Command="{Binding MyCommand}" Content="Button" Height="55" HorizontalAlignment="Left" Margin="166,99,0,0" Name="button1" VerticalAlignment="Top" Width="104" />
<Label Margin="30,99,0,0" Name="label1" Height="55" VerticalAlignment="Top" HorizontalAlignment="Left" Width="101" />
</Grid>
</UserControl>
public partial class UserControl1 : UserControl
{
private UserControl1ViewModel vm;
private static UserControl1 instance;
public UserControl1()
{
InitializeComponent();
instance = this;
DataContext = vm = new UserControl1ViewModel();
}
public string LabelText
{
get { return (string)GetValue(LabelProperty); }
set { SetValue(LabelProperty, value); }
}
public static readonly DependencyProperty LabelProperty =
DependencyProperty.Register("LabelText", typeof(string), typeof(UserControl1), new UIPropertyMetadata(""), OnValidateValueProperty);
private static bool OnValidateValueProperty(object source)
{
if (instance != null)
{
instance.label1.Content = source;
}
return true;
}
}
public class UserControl1ViewModel
{
private DelegateCommand myCommand;
public ICommand MyCommand
{
get
{
if (myCommand == null)
myCommand = new DelegateCommand(new Action<object>(MyExecute),
new Predicate<object>(MyCanExecute));
return myCommand;
}
}
private bool MyCanExecute(object parameter)
{
return true;
}
private void MyExecute(object parameter)
{
MessageBox.Show("Hello World");
}
}
My mainwindow logs as followed:
<Window x:Class="WpfApplication1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525"
xmlns:my="clr-namespace:WpfApplication1">
<Grid>
<my:UserControl1 LabelText="{Binding
Path=DataContext.LabelTextFromMainWindow,
RelativeSource={RelativeSource FindAncestor,
AncestorType={x:Type Window}}}"
HorizontalAlignment="Left"
Margin="114,36,0,0"
x:Name="userControl11"
VerticalAlignment="Top" Height="236" Width="292" />
</Grid>
</Window>
I exspected the following to work correctly.
LabelText="{Binding Path=LabelTextFromMainWindow}"
However, I have to write this one.
LabelText="{Binding Path=DataContext.LabelTextFromMainWindow,
RelativeSource={RelativeSource FindAncestor,
AncestorType={x:Type Window}}}"
What do I have to do in order get the simple Binding to work properly?
By default control inherits DataContext from its parent unless you set it explicitly.
In your case, you explicitly set the DataContext of your UserControl as
DataContext = vm = new UserControl1ViewModel();
which makes all the bindings on your UserControl to look for bindings in class UserControl1ViewModel instead in MainWindowViewModel.
That's why you have to use RelativeSource to get Window's DataContext i.e. you explicitly asked binding to be found in window's DataContext instead in its own DataContext and i see no issue in using RelativeSource.
But, if you want to work like simple binding without RelativeSource, first of all you need to get rid of explicitly setting DataContext and move all commands and properties in MainWindowsViewModel so that your UserControl inherits its DataContext from MainWindow.
OR
You can give name to your window and bind using ElementName -
<Window x:Class="WpfApplication1.MainWindow"
x:Name="MainWindow"> <--- HERE
<Grid>
<my:UserControl1 LabelText="{Binding
Path=DataContext.LabelTextFromMainWindow,
ElementName=MainWindow}"/>
I wrote a customcontrol. It is a textbox with a button which opens a OpenFileDialog.
The Text property of the TextBox is bound to my dependency property "FileName". And if the user selects a file via the OpenFileDialog, i set the result to this property.
The TextBox gets the right value through binding.
But now my problem. For my view I'm using a ViewModel. So I have a Binding to my DependencyProperty "FileName" to the property in my ViewModel.
After changing the "FileName" property (changes direct to the textbox or selecting a file via the dialog), the viewmodel property doesn't update.
CustomControl.xaml.cs
using System.ComponentModel;
using System.Windows;
using System.Windows.Controls;
using Microsoft.Win32;
namespace WpfApplication1.CustomControl
{
/// <summary>
/// Interaction logic for FileSelectorTextBox.xaml
/// </summary>
public partial class FileSelectorTextBox
: UserControl, INotifyPropertyChanged
{
public FileSelectorTextBox()
{
InitializeComponent();
DataContext = this;
}
#region FileName dependency property
public static readonly DependencyProperty FileNameProperty = DependencyProperty.Register(
"FileName",
typeof(string),
typeof(FileSelectorTextBox),
new FrameworkPropertyMetadata(string.Empty,
FrameworkPropertyMetadataOptions.BindsTwoWayByDefault,
new PropertyChangedCallback(OnFileNamePropertyChanged),
new CoerceValueCallback(OnCoerceFileNameProperty)));
public string FileName
{
get { return (string)GetValue(FileNameProperty); }
set { /*SetValue(FileNameProperty, value);*/ CoerceFileName(value); }
}
private bool _shouldCoerceFileName;
private string _coercedFileName;
private object _lastBaseValueFromCoercionCallback;
private object _lastOldValueFromPropertyChangedCallback;
private object _lastNewValueFromPropertyChangedCallback;
private object _fileNameLocalValue;
private ValueSource _fileNameValueSource;
private static void OnFileNamePropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
if (d is FileSelectorTextBox)
{
(d as FileSelectorTextBox).OnFileNamePropertyChanged(e);
}
}
private void OnFileNamePropertyChanged(DependencyPropertyChangedEventArgs e)
{
LastNewValueFromPropertyChangedCallback = e.NewValue;
LastOldValueFromPropertyChangedCallback = e.OldValue;
FileNameValueSource = DependencyPropertyHelper.GetValueSource(this, FileNameProperty);
FileNameLocalValue = this.ReadLocalValue(FileNameProperty);
}
private static object OnCoerceFileNameProperty(DependencyObject d, object baseValue)
{
if (d is FileSelectorTextBox)
{
return (d as FileSelectorTextBox).OnCoerceFileNameProperty(baseValue);
}
else
{
return baseValue;
}
}
private object OnCoerceFileNameProperty(object baseValue)
{
LastBaseValueFromCoercionCallback = baseValue;
return _shouldCoerceFileName ? _coercedFileName : baseValue;
}
internal void CoerceFileName(string fileName)
{
_shouldCoerceFileName = true;
_coercedFileName = fileName;
CoerceValue(FileNameProperty);
_shouldCoerceFileName = false;
}
#endregion FileName dependency property
#region Public Properties
public ValueSource FileNameValueSource
{
get { return _fileNameValueSource; }
private set
{
_fileNameValueSource = value;
OnPropertyChanged("FileNameValueSource");
}
}
public object FileNameLocalValue
{
get { return _fileNameLocalValue; }
set
{
_fileNameLocalValue = value;
OnPropertyChanged("FileNameLocalValue");
}
}
public object LastBaseValueFromCoercionCallback
{
get { return _lastBaseValueFromCoercionCallback; }
set
{
_lastBaseValueFromCoercionCallback = value;
OnPropertyChanged("LastBaseValueFromCoercionCallback");
}
}
public object LastNewValueFromPropertyChangedCallback
{
get { return _lastNewValueFromPropertyChangedCallback; }
set
{
_lastNewValueFromPropertyChangedCallback = value;
OnPropertyChanged("LastNewValueFromPropertyChangedCallback");
}
}
public object LastOldValueFromPropertyChangedCallback
{
get { return _lastOldValueFromPropertyChangedCallback; }
set
{
_lastOldValueFromPropertyChangedCallback = value;
OnPropertyChanged("LastOldValueFromPropertyChangedCallback");
}
}
#endregion FileName dependency property
private void btnBrowse_Click(object sender, RoutedEventArgs e)
{
FileDialog dlg = null;
dlg = new OpenFileDialog();
bool? result = dlg.ShowDialog();
if (result == true)
{
FileName = dlg.FileName;
}
txtFileName.Focus();
}
#region INotifyPropertyChanged
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
#endregion INotifyPropertyChanged
}
}
CustomControl.xaml
<UserControl x:Class="WpfApplication1.CustomControl.FileSelectorTextBox"
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="23" d:DesignWidth="300">
<Border BorderBrush="#FF919191"
BorderThickness="0">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" MinWidth="80" />
<ColumnDefinition Width="30" />
</Grid.ColumnDefinitions>
<TextBox Name="txtFileName"
HorizontalAlignment="Stretch"
VerticalAlignment="Center"
Grid.Column="0"
Text="{Binding FileName}" />
<Button Name="btnBrowse"
Click="btnBrowse_Click"
HorizontalContentAlignment="Center"
ToolTip="Datei auswählen"
Margin="1,0,0,0"
Width="29"
Padding="1"
Grid.Column="1">
<Image Source="../Resources/viewmag.png"
Width="15"
Height="15" />
</Button>
</Grid>
</Border>
</UserControl>
Using in a view:
<Window x:Class="WpfApplication1.MainView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:vm="clr-namespace:WpfApplication1.ViewModels"
xmlns:controls="clr-namespace:WpfApplication1.CustomControl"
Title="MainWindow" Height="350" Width="525">
<Window.DataContext>
<vm:MainViewModel />
</Window.DataContext>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="*" />
<RowDefinition Height="10" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<DataGrid ItemsSource="{Binding Files}" AutoGenerateColumns="False">
<DataGrid.Columns>
<DataGridTemplateColumn Header="File name" Width="*">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<controls:FileSelectorTextBox FileName="{Binding .}" Height="30" />
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
</DataGrid>
<ListBox ItemsSource="{Binding Files}" Grid.Row="2">
<ListBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding}" />
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</Grid>
</Window>
And the ViewModel:
using System.Collections.ObjectModel;
using System.ComponentModel;
namespace WpfApplication1.ViewModels
{
internal class MainViewModel
: INotifyPropertyChanged
{
public MainViewModel()
{
Files = new ObservableCollection<string> { "test1.txt", "test2.txt", "test3.txt", "test4.txt" };
}
#region Properties
private ObservableCollection<string> _files;
public ObservableCollection<string> Files
{
get { return _files; }
set
{
_files = value;
OnPropertyChanged("Files");
}
}
#endregion Properties
#region INotifyPropertyChanged Members
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
#endregion INotifyPropertyChanged Members
}
}
Is there any wrong using of the dependency property?
Note: The problem only occurs in DataGrid.
You need to set binding Mode to TwoWay, because by default binding works one way, i.e. loading changes from the view model, but not updating it back.
<controls:FileSelectorTextBox FileName="{Binding FileName, Mode=TwoWay}" Height="30" />
Another option is to declare your custom dependency property with BindsTwoWayByDefault flag, like this:
public static readonly DependencyProperty FileNameProperty =
DependencyProperty.Register("FileName",
typeof(string),
typeof(FileSelectorTextBox),
new FrameworkPropertyMetadata(default(string), FrameworkPropertyMetadataOptions.BindsTwoWayByDefault));
Also when you change your custom dependency property from inside your control use SetCurrentValue method instead of directly assigning the value using property setter. Because if you assign it directly you will break the binding.
So, instead of:
FileName = dlg.FileName;
Do like this:
SetCurrentValue(FileNameProperty, dlg.FileName);
Change as following:
<TextBox Name="txtFileName"
HorizontalAlignment="Stretch"
VerticalAlignment="Center"
Grid.Column="0"
Text="{Binding FileName, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />
I needed to build a custom treeview as a user control. I called it for the sake of the example TreeViewEx :
<UserControl x:Class="WpfApplication4.TreeViewEx"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
x:Name="root">
<Grid>
<TreeView ItemsSource="{Binding Path=ItemsSource, ElementName=root}">
<TreeView.ItemTemplate>
<HierarchicalDataTemplate ItemsSource="{Binding Children}">
<StackPanel Orientation="Horizontal">
<TextBlock Text="Node : "/>
<ContentControl Content="{Binding Path=AdditionalContent, ElementName=root}"/>
</StackPanel>
</HierarchicalDataTemplate>
</TreeView.ItemTemplate>
</TreeView>
</Grid>
</UserControl>
The idea is to have a fixed part of the content of the ItemTemplate and a customizable part of it.
Of course, I created two dependency properties on the TreeViewEx class :
public partial class TreeViewEx
{
public static readonly DependencyProperty ItemsSourceProperty = DependencyProperty.Register(
"ItemsSource", typeof(IEnumerable), typeof(TreeViewEx));
public IEnumerable ItemsSource
{
get { return (IEnumerable)GetValue(ItemsSourceProperty); }
set { SetValue(ItemsSourceProperty, value); }
}
public static readonly DependencyProperty AdditionalContentProperty = DependencyProperty.Register(
"AdditionalContent", typeof(object), typeof(TreeViewEx));
public object AdditionalContent
{
get { return GetValue(AdditionalContentProperty); }
set { SetValue(AdditionalContentProperty, value); }
}
public TreeViewEx()
{
InitializeComponent();
}
}
Having a simple node class like so :
public class Node
{
public string Name { get; set; }
public int Size { get; set; }
public IEnumerable<Node> Children { get; set; }
}
I would feed the treeview. I place an instance of TreeViewEx on the MainWindow of a WPF test project :
<Window x:Class="WpfApplication4.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525"
xmlns:local="clr-namespace:WpfApplication4">
<Grid>
<local:TreeViewEx x:Name="tree">
<local:TreeViewEx.AdditionalContent>
<TextBlock Text="{Binding Name}"/>
</local:TreeViewEx.AdditionalContent>
</local:TreeViewEx>
</Grid>
</Window>
And finally feed it :
public partial class MainWindow
{
public MainWindow()
{
InitializeComponent();
var dummyData = new ObservableCollection<Node>
{
new Node
{
Name = "Root",
Size = 3,
Children = new ObservableCollection<Node>
{
new Node{
Name="Child1",
Size=2,
Children = new ObservableCollection<Node>{
new Node{
Name = "Subchild",
Size = 1
}
}
}
}
}
};
tree.ItemsSource = dummyData;
}
}
However it doesn't work as expected namely at first the ContentControl has the data but as I expand the nodes it does not display the ContentControl's content.
I don't really get it.. I should use a DataTemplate or something else?
The problem is that you're setting the content to an instance of a control, and that control can only have one parent. When you expand the tree and it adds it to the second node, it removes it from the first one.
As you suspected, you want to supply a DataTemplate to TreeViewEx instead of a control. You can use a ContentPresenter to instantiate the template at each level of the tree:
Replace the AdditionalContentProperty with:
public static readonly DependencyProperty AdditionalContentTemplateProperty = DependencyProperty.Register(
"AdditionalContentTemplate", typeof(DataTemplate), typeof(TreeViewEx));
public DataTemplate AdditionalContentTemplate
{
get { return (DataTemplate)GetValue(AdditionalContentTemplateProperty); }
set { SetValue(AdditionalContentTemplateProperty, value); }
}
change the HierarchicalDataTemplate in your UserControl's XAML to:
<StackPanel Orientation="Horizontal">
<TextBlock Text="Node : "/>
<ContentPresenter ContentTemplate="{Binding Path=AdditionalContentTemplate, ElementName=root}"/>
</StackPanel>
and change MainWindow to:
<local:TreeViewEx x:Name="tree">
<local:TreeViewEx.AdditionalContentTemplate>
<DataTemplate>
<TextBlock Text="{Binding Name}"/>
</DataTemplate>
</local:TreeViewEx.AdditionalContentTemplate>
</local:TreeViewEx>