UserControl DependencyProperty binding in ListView - c#

When I try to use a custom UserControl in a ListView, it fails and only empty blocks are displayed (the following TextBlock works though). While the customControl outside the ListView works pretty well. What's the problem?
MainWindow.xaml
<Grid>
<StackPanel>
<controls:CustomControl x:Name="customControl" CustomText="Test"/>
<ListView x:Name="listView">
<ListView.ItemTemplate>
<DataTemplate>
<StackPanel>
<controls:CustomControl CustomObject="{Binding}"/>
<TextBlock Text="{Binding Text}"/>
</StackPanel>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</StackPanel>
</Grid>
MainWindow.cs
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
InitializeMyComponent();
}
public System.Collections.ObjectModel.ObservableCollection<CustomClass> CustomCollection { get; set; }
private void InitializeMyComponent()
{
this.CustomCollection = new System.Collections.ObjectModel.ObservableCollection<CustomClass>();
this.CustomCollection.Add(new CustomClass() { Number = 1, Text = "a" });
this.CustomCollection.Add(new CustomClass() { Number = 2, Text = "b" });
this.CustomCollection.Add(new CustomClass() { Number = 3, Text = "c" });
this.listView.ItemsSource = this.CustomCollection;
this.customControl.Custom = new CustomClass() { Number = 0, Text = "customControl" };
}
}
CustomControl.xaml
<Grid>
<StackPanel>
<TextBlock>
<Run x:Name="numberRun" Text="{Binding CustomObject.Number}"/>
<Run x:Name="textRun" Text="{Binding CustomObject.Text}"/>
<Run Text="{Binding CustomText}"/>
</TextBlock>
</StackPanel>
</Grid>
CustomControl.cs
public partial class CustomControl : UserControl
{
public static readonly DependencyProperty CustomObjectProperty;
public static readonly DependencyProperty CustomTextProperty;
static CustomControl()
{
CustomObjectProperty = DependencyProperty.Register("CustomObject", typeof(CustomClass), typeof(CustomControl), new PropertyMetadata(default(CustomClass), OnCustomObjectPropertyChanged));
CustomTextProperty = DependencyProperty.Register("CustomText", typeof(string), typeof(CustomControl), new PropertyMetadata(string.Empty, OnCustomTextPropertyChanged));
}
public CustomControl()
{
InitializeComponent();
this.DataContext = this;
}
public CustomClass CustomObject
{
get
{
return (CustomClass)(this.GetValue(CustomObjectProperty));
}
set
{
this.SetValue(CustomObjectProperty, value);
}
}
public string CustomText
{
get
{
return (string)(this.GetValue(CustomTextProperty));
}
set
{
this.SetValue(CustomTextProperty, value);
}
}
private static void OnCustomObjectPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) { }
private static void OnCustomTextPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) { }
}
CustomClass.cs
public class CustomClass : INotifyPropertyChanged
{
private int number;
private string text;
public CustomClass()
{
this.number = new int();
this.text = string.Empty;
}
public int Number
{
get
{
return this.number;
}
set
{
this.number = value;
this.OnPropertyChanged();
}
}
public string Text
{
get
{
return this.text;
}
set
{
this.text = value;
this.OnPropertyChanged();
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged([System.Runtime.CompilerServices.CallerMemberName] string name = null)
{
PropertyChangedEventHandler handler = this.PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(name));
}
}
}

Setting a UserControl's DataContext to the UserControl instance like
this.DataContext = this;
effectively prevents bindings to an "external", inherited DataContext, as expected in
<controls:CustomControl CustomObject="{Binding}"/>
As a general rule, never set a UserControl's DataContext explicitly. Remove the above line from the UserControl's constructor, and write the bindings in the UserControl's XAML with RelativeSource:
<Run x:Name="numberRun" Text="{Binding CustomObject.Number,
RelativeSource={RelativeSource AncestorType=UserControl}}"/>

Related

Get checked items from a listbox

I'm just getting used to MVVM and want to do without the code-behind and define everything in the view-models.
the combobox represents several selection options (works). I would like to query the elements that have been checked.
Unfortunately I can't access them. The textbox should display all selected elements as concatenated string.
View-Model
class MainViewModel : BaseViewModel
{
#region Fields
private ObservableCollection<EssayTypeViewModel> _essayTypes;
private EssayTypeViewModel _selectedEssayTypes;
#endregion
public ObservableCollection<EssayTypeViewModel> EssayTypes
{
get => _essayTypes;
set
{
if (_essayTypes == value) return;
_essayTypes = value; OnPropertyChanged("EssayTypes");
}
}
public EssayTypeViewModel SelectedEssayTypes
{
get => _selectedEssayTypes;
set { _selectedEssayTypes = value; OnPropertyChanged("SelectedEssayTypes"); }
}
public MainViewModel()
{
// Load Essay Types
EssayTypeRepository essayTypeRepository = new EssayTypeRepository();
var essayTypes = essayTypeRepository.GetEssayTypes();
var essayTypeViewModels = essayTypes.Select(m => new EssayTypeViewModel()
{
Text = m.Text
});
EssayTypes = new ObservableCollection<EssayTypeViewModel>(essayTypeViewModels);
}
}
XAML
<ListBox x:Name="Listitems" SelectionMode="Multiple" Height="75" Width="200" ItemsSource="{Binding EssayTypes}" >
<ListBox.ItemTemplate>
<DataTemplate>
<CheckBox Content="{Binding Text}" IsChecked="{Binding Checked}"/>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
<TextBox Text="{Binding Path=SelectedEssayTypes}" Grid.Column="0" Width="160" Height="25" Margin="0,140,0,0"/>
You could hook up an event handler to the PropertyChanged event of all EssayTypeViewModel objects in the EssayTypes collection and raise the PropertyChanged event for a read-only property of the MainViewModel that returns all selected elements as concatenated string:
public MainViewModel()
{
// Load Essay Types
EssayTypeRepository essayTypeRepository = new EssayTypeRepository();
var essayTypes = essayTypeRepository.GetEssayTypes();
var essayTypeViewModels = essayTypes.Select(m =>
{
var vm = EssayTypeViewModel()
{
Text = m.Text
};
vm.PropertyChanged += OnPropertyChanged;
return vm;
});
EssayTypes = new ObservableCollection<EssayTypeViewModel>(essayTypeViewModels);
}
private void OnPropertyChanged(object sender, PropertyChangedEventArgs e)
{
if (e.PropertyName == "Checked")
OnPropertyChanged("SelectedItems");
}
public string SelectedItems => string.Join(",", EssayTypes.Where(x => x.Checked).ToArray());
This requires the EssayTypeViewModel class to implement the INotifyPropertyChanged interface (by for example deriving from your BaseViewModel class).
You can apply Mode = Two way on the checkbox binding.
<CheckBox Content="{Binding Text}" IsChecked="{Binding Checked, Mode=TwoWay}"/>
then you can iterate through the essay types collection to check if the item entry was checked.
For ex. Sample code can be:
foreach (var essayTypeInstance in EssayTypes)
{
if(essayTypeInstance.Checked)
{
// this value is selected
}
}
Hope this helps.
mm8 answer works. In the meantime i came up with another approach. Not 100% MVVM compatible but it works and is quite simple.
XAML
<ListBox x:Name="ListItems" SelectionMode="Multiple" Height="75" Width="200" ItemsSource="{Binding CollectionOfItems}" >
<ListBox.ItemTemplate>
<DataTemplate>
<CheckBox Content="{Binding Name}" IsChecked="{Binding Checked, Mode=TwoWay}" Unchecked="GetCheckedElements" Checked="GetCheckedElements" />
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
<TextBox Text="{Binding SelectedItemsString, UpdateSourceTrigger=PropertyChanged}" Grid.Column="0" Width="160" Height="25" Margin="0,140,0,0"/>
Code Behind
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
DataContext = new MainViewModel();
}
private void GetCheckedElements(object sender, RoutedEventArgs e)
{
(DataContext as MainViewModel)?.FindCheckedItems();
(DataContext as MainViewModel)?.ConcatSelectedElements();
}
}
Model
public class Items
{
public bool Checked { get; set; }
public string Name { get; set; }
}
ItemsViewModel (BaseViewModel only implements INotifyPropertyChanged)
class ItemsViewModel : BaseViewModel
{
private bool _checked;
private string _name;
public bool Checked
{
get => _checked;
set
{
if (value == _checked) return;
_checked = value;
OnPropertyChanged("Checked");
}
}
public string Name
{
get => _name;
set
{
if (value == _name) return;
_name = value;
OnPropertyChanged("Name");
}
}
}
MainViewModel
public class MainViewModel : BaseViewModel
{
private string _selectedItemsString;
private ObservableCollection<Items> _selectedItems;
public ObservableCollection<Items> CollectionOfItems { get; set; }
public ObservableCollection<Items> SelectedItems
{
get => _selectedItems;
set
{
_selectedItems = value;
OnPropertyChanged("SelectedItems");
}
}
public string SelectedItemsString
{
get => _selectedItemsString;
set
{
if (value == _selectedItemsString) return;
_selectedItemsString = value;
OnPropertyChanged("SelectedItemsString");
}
}
public MainViewModel()
{
CollectionOfItems = new ObservableCollection<Items>();
SelectedItems = new ObservableCollection<Items>();
CollectionOfItems.Add(new Items { Checked = false, Name = "Item 1" });
CollectionOfItems.Add(new Items { Checked = false, Name = "Item 2" });
CollectionOfItems.Add(new Items { Checked = false, Name = "Item 3" });
}
public void FindCheckedItems()
{
CollectionOfItems.Where(x => x.Checked).ToList().ForEach(y => SelectedItems.Add(y));
}
public void ConcatSelectedElements()
{
SelectedItemsString = string.Join(", ", CollectionOfItems.Where(x => x.Checked).ToList().Select(x => x.Name)).Trim();
}
}

How to debug INPC property setter not triggered?

Overview:
I've set up a property with INPC that invokes a page navigation in the view code behind from the MainViewModel. This property is bound to the SelectedItem property of a list view in the bound view.
The INPC implementation is inherited from the ViewModelBase class which is implemented as follows, https://gist.github.com/BrianJVarley/4a0890b678e037296aba
Issue:
When I select an item from the list view, the property SelectedCouncilItem setter doesn't trigger. This property is bound to the SelectedItem property of the list view.
Debugging Steps:
Checked binding names for SelectedItem in list view property, which was the same as the property name in the MainViewModel.
Ran the solution and checked for any binding errors in the output window, which there were none.
Placed a break point on the SelectedCouncilItem which doesn't get triggered when I select from the list view.
Checked the data context setup for the view which verified that the view is set to the data context of the MainViewModel.
Question:
Does anyone know what other steps I can take in debugging the issue, or what the issue might be?
Code:
MainPage - (List View)
<Grid x:Name="ContentPanel"
Grid.Row="1"
Margin="12,0,12,0">
<phone:LongListSelector x:Name="MainLongListSelector"
Margin="0,0,-12,0"
ItemsSource="{Binding Items}"
SelectedItem="{Binding SelectedCouncilItem}">
<phone:LongListSelector.ItemTemplate>
<DataTemplate>
<StackPanel Margin="0,0,0,17">
<TextBlock Style="{StaticResource PhoneTextExtraLargeStyle}"
Text="{Binding CouncilAcronym}"
TextWrapping="Wrap" />
<TextBlock Margin="12,-6,12,0"
Style="{StaticResource PhoneTextSubtleStyle}"
Text="{Binding CouncilFullName}"
TextWrapping="Wrap" />
</StackPanel>
</DataTemplate>
</phone:LongListSelector.ItemTemplate>
</phone:LongListSelector>
</Grid>
MainViewModel - (summary)
namespace ParkingTagPicker.ViewModels
{
public class MainViewModel : ViewModelBase
{
//Dependency Injection private instances
private INavigationCallback _navCallBack = null;
public MainViewModel()
{
this.Items = new ObservableCollection<ItemViewModel>();
}
/// <summary>
/// Creates and adds a few ItemViewModel objects into the Items collection.
/// </summary>
public void LoadCouncilNamesData()
{
this.Items.Add(new ItemViewModel() { ID = "6", CouncilAcronym = "WTC", CouncilFullName = "Wicklow Town Council"});
this.Items.Add(new ItemViewModel() { ID = "7", CouncilAcronym = "TS", CouncilFullName = "Tallaght Stadium" });
this.Items.Add(new ItemViewModel() { ID = "8", CouncilAcronym = "GS", CouncilFullName = "Greystones" });
this.IsDataLoaded = true;
}
public ObservableCollection<ItemViewModel> Items { get; private set; }
public bool IsDataLoaded { get; private set; }
private ItemViewModel _selectedCouncilItem;
public ItemViewModel SelectedCouncilItem
{
get
{
return this._selectedCouncilItem;
}
set
{
this.SetProperty(ref this._selectedCouncilItem, value, () => this._selectedCouncilItem);
if (_selectedCouncilItem != null)
{
_navCallBack.NavigateTo(_selectedCouncilItem.ID);
}
}
}
public INavigationCallback NavigationCallback
{
get { return _navCallBack; }
set { _navCallBack = value; }
}
}
}
ViewModelBase - (detailing INPC implementation)
namespace ParkingTagPicker.ViewModels
{
public abstract class ViewModelBase : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected void RaisePropertyChanged(string propertyName)
{
var propertyChanged = this.PropertyChanged;
if (propertyChanged != null)
{
propertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
protected bool SetProperty<T>(ref T backingField, T Value, Expression<Func<T>> propertyExpression)
{
var changed = !EqualityComparer<T>.Default.Equals(backingField, Value);
if (changed)
{
backingField = Value;
this.RaisePropertyChanged(ExtractPropertyName(propertyExpression));
}
return changed;
}
private static string ExtractPropertyName<T>(Expression<Func<T>> propertyExpression)
{
var memberExp = propertyExpression.Body as MemberExpression;
if (memberExp == null)
{
throw new ArgumentException("Expression must be a MemberExpression.", "propertyExpression");
}
return memberExp.Member.Name;
}
}
}
There is an issue with the control. Please try using custom LongListSeletor
public class ExtendedLongListSelector : Microsoft.Phone.Controls.LongListSelector
{
public ExtendedLongListSelector()
{
SelectionChanged += LongListSelector_SelectionChanged;
}
void LongListSelector_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
SelectedItem = base.SelectedItem;
}
public static readonly DependencyProperty SelectedItemProperty = DependencyProperty.Register("SelectedItem", typeof(object), typeof(LongListSelector),
new PropertyMetadata(null, OnSelectedItemChanged));
private static void OnSelectedItemChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var selector = (LongListSelector)d;
selector.SelectedItem = e.NewValue;
}
public new object SelectedItem
{
get { return GetValue(SelectedItemProperty); }
set { SetValue(SelectedItemProperty, value); }
}
}
and implement in replace it in XAML with the existing List.
xmlns:controls="clr-namespace:ProjectName.FolderName"
<controls:ExtendedLongListSelector x:Name="MainLongListSelector"
Margin="0,0,-12,0"
ItemsSource="{Binding Items}"
SelectedItem="{Binding SelectedCouncilItem}">
</controls:ExtendedLongListSelector>

Binding a ObservableCollection<int> to IEnumerable<object> of a custom control

With some help, I recently made binding collections in my custom control work. However, to my surprise I was told that to make the custom control property more flexible (that is possible to be bound with other parametrized collections), I needed to make the custom control's property to be of type IEnumerable<object> because of covariance and contravariance. However, this seems not to work for me
This is the control's view
<UserControl x:Class="BadaniaOperacyjne.Controls.Matrix"
mc:Ignorable="d" Name="CustomMatrix"
d:DesignHeight="300" d:DesignWidth="300">
<Grid>
<!-- ... -->
<Grid Grid.Row="2" Grid.Column="1" Name="contentGrid">
<ListBox ItemsSource="{Binding ElementName=CustomMatrix, Path=ItemsList}">
<ListBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding}"/>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</Grid>
</Grid>
</UserControl>
and its code-behind is here
#region ItemsList Property
public static readonly DependencyProperty ItemsListProperty =
DependencyProperty.Register("ItemsList", typeof(IEnumerable<object>), typeof(Matrix), new PropertyMetadata(new PropertyChangedCallback(ItemsListChanged)));
public IEnumerable<object> ItemsList
{
get { return GetValue(ItemsListProperty) as IEnumerable<object>; }
set { SetValue(ItemsListProperty, value); }
}
private void ItemsListChanged(object value)
{
System.Diagnostics.Debug.WriteLine("matrix: items list changed " + value);
if (ItemsList != null)
{
//ItemsList.CollectionChanged += ItemsList_CollectionChanged;
System.Diagnostics.Debug.WriteLine("got " + string.Join(",", ItemsList.ToList()));
}
else
{
System.Diagnostics.Debug.WriteLine("got null");
}
}
void ItemsList_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
{
System.Diagnostics.Debug.WriteLine("matrix: current items list collection changed");
}
private static void ItemsListChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
((Matrix)d).ItemsListChanged(e.NewValue);
}
#endregion
and the Window that consumes the control is the following
<custom:Matrix x:Name="customMatrix" DockPanel.Dock="Top" Title="{Binding Title}" ItemsList="{Binding Items}"/>
with the code-behind like
public class ViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public ViewModel()
{
Items = new ObservableCollection<int> { 1, 2, 3, 4, 5, 6};
Items.CollectionChanged += Items_CollectionChanged;
}
void Items_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
{
System.Diagnostics.Debug.WriteLine("problem manager: items list changed " + e.NewItems.Count);
}
public ObservableCollection<int> Items { get; private set; }
protected string title;
public string Title
{
get { return title; }
set
{
if (title != value)
{
title = value;
NotifyPropertyChanged("Title");
}
}
}
protected void NotifyPropertyChanged(string name)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(name));
}
}
}
public ViewModel VM { get; private set; }
// the window's constructor
private ProblemManager()
{
VM = new ViewModel();
DataContext = VM;
InitializeComponent();
VM.Title = "title";
}
private int i = 0;
private void btnAddRow_Click(object sender, RoutedEventArgs e)
{
//VM.Items.Add(++i);
VM.Items[2] = 112;
//customMatrix.ItemsList = new ObservableCollection<object> { 1, 2, 3 };
//customMatrix.ItemsList.Add(66);
//////
VM.Title = (++i).ToString();
}
When I change the DependencyProperty of the ItemsList control to ObservableCollection<int> or at least ObservableCollection<object>, it works fine.
Is it really possible? If so, then is the mistake I made?
Co-variance is allowed for IEnumerable but i just checked its only allowed for reference types and not for value types (e.g. int).
Your version will work if you bind with ObservableCollection<string> since string is reference type.
So what you can do is use IEnumerable (non-generic version) as return type of your DP like this so that it will work for value types as well:
public static readonly DependencyProperty ItemsListProperty =
DependencyProperty.Register("ItemsList", typeof(IEnumerable), typeof(Matrix),
new PropertyMetadata(new PropertyChangedCallback(ItemsListChanged)));
public IEnumerable ItemsList
{
get { return (IEnumerable)GetValue(ItemsListProperty); }
set { SetValue(ItemsListProperty, value); }
}

ItemsControl TextBox Name is not Working in .cs File

My WPF Application code generates panels on function call defined in .cs file. There is ItemControl used in code to generates these Panels . I want to Name Textbox defined in this ItemControl and to use this in code. I named it as textEdit1 and used it in code but code generated error that textEdit1 doesn't exist. Can anyone solve my problem? Here Code is:
XAML File:
<dxlc:ScrollBox>
<ItemsControl Name="lstPanels">
<ItemsControl.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="vertical">
<Grid>
<dxe:TextEdit Height="165" Text="{Binding Text,
Mode=TwoWay}" x:Name="textEdit1"/>
</Grid>
</StackPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</dxlc:ScrollBox>
.CS FILE
public partial class Window1 : Window
{
string valuu;
public Window1()
{
InitializeComponent();
addPanel("Header1");
addPanel("Header2");
addPanel("Header3");
lstPanels.ItemsSource = panels;
}
public ObservableCollection<MyPanel> panels = new ObservableCollection<MyPanel>();
public void addPanel(string buttonId)
{
MyPanel p = new MyPanel { Id = buttonId};
panels.Add(p);
functionb(p);
}
public void functionb(MyPanel obj)
{
valuu = obj.Text;
}
private void button2_Click(object sender, RoutedEventArgs e)
{
foreach (var f in panels.ToList())
{
MessageBox.Show( f.Id + " *** " + f.Text);
}
}
}
public class MyPanel : INotifyPropertyChanged
{
private string _id;
private string _text;
public string Id
{
get { return _id; }
set
{
if (value != _id)
{
_id = value;
NotifyPropertyChanged();
}
}
}
public string Text
{
get { return _text; }
set
{
if (value != _text)
{
_text = value;
NotifyPropertyChanged();
}
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected void NotifyPropertyChanged( String propertyName = "")
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
I see that you are using some 3rd party libraries for your TextBox and ScrollBox. If you provide me with the names of the libraries, I could have a look at them as the functionality might be different from what WPF has out-of-the-box.
As for now you have 3 options (I am giving examples for standard TextBox and ItemsControl):
I) You do not have to access the textbox at all.
An easy way around it is described here: StackOverflow post
II) Handling events and references to TextBoxes in the code behind
Add a Loaded event to your TextBox:
<TextBox x:Name="txtText" Width="300" Height="100" Loaded="txtText_Loaded" />
Add a field to your MyPanel class to hold a reference to a TextBox:
public class MyPanel
{
public string Text { get; set; }
public TextBox TextBox { get; set; }
/* the rest ... */
}
Add a counter to your window, next to a list with panels:
protected ObservableCollection<MyPanel> panels = new ObservableCollection<MyPanel>();
private int counter = 0;
Handle the Load event of the TextBox:
private void txtText_Loaded(object sender, RoutedEventArgs e)
{
panels[counter].TextBox = (TextBox)sender;
counter++;
}
If you want to access a particular TextBox, do it this way:
MessageBox.Show(panels[i].TextBox.Text);
III) Add additional bindings for FontSize:
Add a FontSize property to your MyPanel class:
private double _fontSize = 10;
public double FontSize
{
get { return _fontSize; }
set
{
if (value != _fontSize)
{
_fontSize = value;
NotifyPropertyChanged();
}
}
}
Bind just added property to the TextBox in your ItemsControl:
<TextBox x:Name="txtText" Width="300" Height="100" Text="{Binding Text;, Mode=TwoWay}"
FontSize="{Binding FontSize, Mode=OneWay}" />
Add a slider to the template and bind it to the same property:
<Slider Minimum="10" Maximum="30" Value="{Binding FontSize, Mode=TwoWay}" />
This way if you change the value on a slider, it will change the value in your MyPanel object bound to the panel. This in turn will change the font size of the textbox.
My whole code I tested it on looks like that:
<ItemsControl x:Name="lstItems" >
<ItemsControl.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Vertical">
<TextBox x:Name="txtText" Width="300" Height="100" Text="{Binding Text;, Mode=TwoWay}" FontSize="{Binding FontSize, Mode=OneWay}" />
<Slider Minimum="10" Maximum="30" Value="{Binding FontSize, Mode=TwoWay}" />
</StackPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
And code behind:
public partial class MainWindow : Window
{
protected ObservableCollection<MyPanel> texts = new ObservableCollection<MyPanel>();
public MainWindow()
{
InitializeComponent();
texts.Add(new MyPanel() { Text = "Test 1" });
texts.Add(new MyPanel() { Text = "Test 2" });
lstItems.ItemsSource = texts;
}
}
public class MyPanel : INotifyPropertyChanged
{
private string _id;
private string _text;
private double _fontSize = 10;
public string Id
{
get { return _id; }
set
{
if (value != _id)
{
_id = value;
NotifyPropertyChanged();
}
}
}
public string Text
{
get { return _text; }
set
{
if (value != _text)
{
_text = value;
NotifyPropertyChanged();
}
}
}
public double FontSize
{
get { return _fontSize; }
set
{
if (value != _fontSize)
{
_fontSize = value;
NotifyPropertyChanged();
}
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected void NotifyPropertyChanged(String propertyName = "")
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
I personally would go with the last solution.
But again, let me know what libraries you are using, and I will have look at them when I have some time. Good luck.
textEdit1 is part of a template that will be instantiated multiple times, so there will be multiple instances of textEdit1. It wouldn't make sense to generate a field for textEdit1 in the class, because it could only refer to one instance the TextEdit control...

When does binding target get updated for complex paths?

When using databinding in WPF, the target dependency object gets updated when it is notified that the source has changed through the INotifyPropertyChanged interface.
For example:
<TextBlock Text="{Binding Path=SomeField}"/>
The text field will change to correctly reflect the value of SomeField whenever PropertyChanged(this, new PropertyChangedEventArgs("SomeField")) is called from the source.
What if I use a complex path like the following:
<TextBlock Text="{Binding Path=SomeObjField.AnotherField}"/>
Will the text field get updated for PropertyChanged(this, new PropertyChangedEventArgs("SomeObjField")) on the source?
What about PropertyChanged(this, new PropertyChangedEventArgs("AnotherField")) on the intermediate object (the object contained within the SomeObjField)?
Source objects and fields are NOT dependency objects or properties! Assume that the property/classes are implemented something like the following:
public class Data : INotifyPropertyChanged
{
// INotifyPropertyChanged implementation...
public string SomeField
{
get { return val; }
set
{
val = value;
// fire PropertyChanged()
}
}
public SubData SomeObjField
{
get { return val; }
set
{
val = value;
// fire PropertyChanged()
}
}
}
public class SubData : INotifyPropertyChanged
{
// INotifyPropertyChanged implementation...
public string AnotherField
{
get { return val; }
set
{
val = value;
// fire PropertyChanged()
}
}
}
After further investigation, it appears that when any part of the complex path sends a change notification the binding is updated. Thus, if the source object OR the intermediate object is changed the binding will be updated.
I built a test project like Jared's:
<StackPanel Name="m_panel">
<TextBox IsReadOnly="True" Text="{Binding Path=SomeObjField.AnotherField }" />
<TextBox x:Name="field1"/>
<Button Click="Button1_Click">Edit Root Object</Button>
<TextBox x:Name="field2"/>
<Button Click="Button2_Click">Edit Sub Object</Button>
</StackPanel>
And the code behind:
public Window1()
{
InitializeComponent();
m_panel.DataContext = new Data();
}
private void Button1_Click(object sender, RoutedEventArgs e)
{
Data d = m_panel.DataContext as Data;
d.SomeObjField = new SubData(field1.Text);
}
private void Button2_Click(object sender, RoutedEventArgs e)
{
Data d = m_panel.DataContext as Data;
d.SomeObjField.AnotherField = field2.Text;
}
I am using the basic data implementation that I provided in the question.
I'm not 100% sure what your asking with the PropertyChanged part of the question. But if the properties involved are all DependencyProperty backed properties then this should work as expected. I drew up the following example
Window1.xaml
<Window x:Class="WpfApplication1.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Window1" Height="300" Width="300">
<StackPanel Name="m_panel">
<TextBlock Text="{Binding Path=SomeField}" />
<TextBlock Text="{Binding Path=SomeField.AnotherField }" />
<Button Click="Button_Click">Update Root Object</Button>
<Button Click="Button_Click_1">Update Another Field</Button>
</StackPanel>
</Window>
Window1.xaml.cs
public partial class Window1 : Window
{
public Window1()
{
InitializeComponent();
m_panel.DataContext = new Class1();
}
private void Button_Click(object sender, RoutedEventArgs e)
{
((Class1)m_panel.DataContext).SomeField = new Class2();
}
private void Button_Click_1(object sender, RoutedEventArgs e)
{
((Class1)m_panel.DataContext).SomeField.AnotherField = "Updated field";
}
}
And the classes
public class Class1 : DependencyObject
{
public static DependencyProperty SomeFieldProperty = DependencyProperty.Register(
"SomeField",
typeof(Class2),
typeof(Class1));
public Class2 SomeField
{
get { return (Class2)GetValue(SomeFieldProperty); }
set { SetValue(SomeFieldProperty, value); }
}
public Class1()
{
SomeField = new Class2();
}
}
public class Class2 : DependencyObject
{
public static DependencyProperty AnotherFieldProperty = DependencyProperty.Register(
"AnotherField",
typeof(string),
typeof(Class2));
public string AnotherField
{
get { return (string)GetValue(AnotherFieldProperty); }
set { SetValue(AnotherFieldProperty, value); }
}
public Class2()
{
AnotherField = "Default Value";
}
}

Categories

Resources