Confused about Binding in UserControl - c#

I'm creating a UserControl object, and i'm trying to assign values using Binding (with PropertyChanged). I made a prototype, when I assign value in ViewModel , the value does not appear or modify the UserControl component, but if I assign the value directly in the UserControl object that is in view, the modification works. I would like to understand what I'm doing wrong, since works fine if i just add an object on my window and binding directly (again, using PropertyChanged).
Follow the code below.
Thanks for any help.
Best Regards,
Gustavo.
UserControl:
<UserControl x:Class="WpfControlLibrary1.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="30" d:DesignWidth="300">
<Grid>
<TextBlock Text="{Binding Title}" />
</Grid>
</UserControl>
Code Behind of User Control:
public partial class UserControl1 : UserControl
{
public UserControl1()
{
InitializeComponent();
}
#region Title Property
public static String GetTitle(DependencyObject obj)
{
return (String)obj.GetValue(TitleProperty);
}
public static void SetTitle(DependencyObject obj, String value)
{
obj.SetValue(TitleProperty, value);
}
public static readonly DependencyProperty TitleProperty =
DependencyProperty.RegisterAttached(
"Title",
typeof(String),
typeof(UserControl1),
new FrameworkPropertyMetadata(TitleChangedCallback)
);
private static void TitleChangedCallback(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
UserControl1 _this = (d as UserControl1);
}
private String title;
public String Title
{
get { return title; }
set
{
title = value;
OnPropertyChanged("Title");
}
}
#endregion
#region INotifyPropertyChanged event and method
public event PropertyChangedEventHandler PropertyChanged;
// Create the OnPropertyChanged method to raise the event
protected void OnPropertyChanged(string name)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(name));
}
}
#endregion
** My Window: **
<Window x:Class="WpfApplication1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:uc="clr-namespace:WpfControlLibrary1;assembly=WpfControlLibrary1"
Title="MainWindow" Height="350" Width="525">
<Grid>
<uc:UserControl1 Title="{Binding TitleVM, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />
</Grid>
</Window>
** My Code-Behind/ViewModel: **
public partial class MainWindow : INotifyPropertyChanged
{
// Notify WPF that Counter changed
public event PropertyChangedEventHandler PropertyChanged;
public MainWindow()
{
InitializeComponent();
TitleVM = "açsldkfjasçldkfj";
}
private String titleVM;
public String TitleVM
{
get { return titleVM; }
set
{
titleVM = value;
OnPropertyChanged("TitleVM");
}
}
// Create the OnPropertyChanged method to raise the event
protected void OnPropertyChanged(string name)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(name));
}
}
}

Your Window doesnt have a DataContext. Bindings cannot be resolved.
Try this:
public MainWindow()
{
InitializeComponent();
DataContext = this; //This is what you're missing!
TitleVM = "açsldkfjasçldkfj";
}
Edit:
You're also missing the same thing in the UserControl
public UserControl1()
{
InitializeComponent();
DataContext = this;
}

With help of HighCore, i've found one problem, and the other problem was my UserControl doesn't have a name defined.
Just put:
x:Name="UserControl"
Into XAML of UserControl and will work. Also, i've modified my code behind to this:
public String Title
{
get { return (String)base.GetValue(TitleProperty); }
set { base.SetValue(TitleProperty, value); }
}
public static readonly DependencyProperty TitleProperty =
DependencyProperty.RegisterAttached(
"Title",
typeof(String),
typeof(UserControl1),
new FrameworkPropertyMetadata(null)
);
A clean code to our eyes.
PS: There's no need to put:
DataContext = this;
On Code Behind of UserControl. At least here only result on bug, no values was comming.
Thanks HighCore for you help.

Related

Property doesn't update when set in the Business Logic

I need to set a property in the Business Logic with a method in the Business Logic. If you run my code you can see the first String "Target Location" changes successfully, but the second one "Some Other String" doesn't change its value in the view. "PropertyChanged" in the BusinessLogic.cs is null. I have absolutely no idea WHY it's null! Can someone explain me this behaviour and how I can fix this?
I have the following files in my project:
MainWindow.xaml
<Window x:Class="TestWpf.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:TestWpf"
mc:Ignorable="d"
Title="MainWindow" Height="350" Width="525">
<StackPanel>
<TextBox Text="{Binding Path=TargetLocation}"></TextBox>
<TextBox Text="{Binding Path=SomeOtherString}"></TextBox>
<Button Click="ChangeTextButton_Click">Change Target Location</Button>
<Button Click="ChangeSomeOtherStringButton_Click">Change some other string</Button>
</StackPanel>
MainWindow.xaml.cs
public MainWindow()
{
InitializeComponent();
MainViewModel mainViewModel = new MainViewModel();
mainViewModel.TargetLocation = #"A:\Old_Location";
mainViewModel.SomeOtherString = "Old String...";
DataContext = mainViewModel;
}
private void ChangeTextButton_Click(object sender, RoutedEventArgs e)
{
MainViewModel mainViewModel = (MainViewModel)DataContext;
mainViewModel.TargetLocation = #"B:\New_Location";
}
private void ChangeSomeOtherStringButton_Click(object sender, RoutedEventArgs e)
{
MainViewModel mainViewModel = (MainViewModel)DataContext;
mainViewModel.ChangeSomeOtherString();
}
MainViewModel.cs
public class MainViewModel : INotifyPropertyChanged
{
private string targetLocation;
public string TargetLocation
{
get
{
return targetLocation;
}
set
{
targetLocation = value;
OnPropertyChanged("TargetLocation");
}
}
public string SomeOtherString
{
get
{
return BusinessLogicClass.GetInstance().SomeOtherString;
}
set
{
BusinessLogicClass.GetInstance().SomeOtherString = value;
OnPropertyChanged("SomeOtherString");
}
}
public void ChangeSomeOtherString()
{
BusinessLogicClass.GetInstance().ChangeSomeOtherString();
}
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
BusinessLogicClass
public class BusinessLogicClass : INotifyPropertyChanged
{
private static BusinessLogicClass instance;
public static BusinessLogicClass GetInstance()
{
if (instance == null)
{
instance = new BusinessLogicClass();
}
return instance;
}
private BusinessLogicClass()
{
}
private string someOtherString;
public string SomeOtherString
{
get
{
return someOtherString;
}
set
{
someOtherString = value;
OnPropertyChanged("SomeOtherString");
}
}
public void ChangeSomeOtherString()
{
SomeOtherString = "New String!";
}
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
"PropertyChanged" in the BusinessLogic.cs is null. I have absolutely no idea WHY it's null!
PropertyChanged in the BusinessLogic class is null because there are no bindings that use properties in this class as their source. The source properties for both of your bindings are on your MainViewModel class.
WPF doesn't scan through all classes that happen to implement INotifyPropertyChanged. And even if it did, how would it know that a PropertyChanged event fired from your BusinessLogic class means that it needs to update the TextBox bound to the SomeOtherString property on your MainViewModel? WPF can't read your code to find this out.
The simplest fix is to fire a PropertyChanged event inside your ChangeSomeOtherString() method:
public void ChangeSomeOtherString()
{
BusinessLogicClass.GetInstance().ChangeSomeOtherString();
OnPropertyChanged("SomeOtherString"); // Add this line
}
This way WPF knows that the value of the SomeOtherString property has changed and will perform the necessary update to the TextBox.

WPF Data binding Label content

I'm trying to create a simple WPF Application using data binding.
The code seems fine, but my view is not updating when I'm updating my property.
Here's my XAML:
<Window x:Class="Calculator.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:calculator="clr-namespace:Calculator"
Title="MainWindow" Height="350" Width="525"
Name="MainWindowName">
<Grid>
<Label Name="MyLabel" Background="LightGray" FontSize="17pt" HorizontalContentAlignment="Right" Margin="10,10,10,0" VerticalAlignment="Top" Height="40"
Content="{Binding Path=CalculatorOutput, UpdateSourceTrigger=PropertyChanged}"/>
</Grid>
</Window>
Here's my code-behind:
namespace Calculator
{
public partial class MainWindow
{
public MainWindow()
{
DataContext = new CalculatorViewModel();
InitializeComponent();
}
}
}
Here's my view-model
namespace Calculator
{
public class CalculatorViewModel : INotifyPropertyChanged
{
private String _calculatorOutput;
private String CalculatorOutput
{
set
{
_calculatorOutput = value;
NotifyPropertyChanged();
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void NotifyPropertyChanged([CallerMemberName] String propertyName = "")
{
var handler = PropertyChanged;
if (handler != null)
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
}
I'm can't see what I am missing here?? o.O
CalculatorOutput has no getter. How should the View get the value? The Property has to be public as well.
public String CalculatorOutput
{
get { return _calculatorOutput; }
set
{
_calculatorOutput = value;
NotifyPropertyChanged();
}
}

DependencyProperties not firing callbacks as expected in UserControl

I have a very simple example of a DependencyProperty I've registered on a UserControl which is not quite working as expected.
I am binding this property to a DP (called Test) in MainWindow which seems to be firing the OnPropertyChanged events every time it is changed, but the DependencyProperty in my UserControl that is the target of this binding only seems to get notified the first time this property is changed.
Here is what I'm trying to do in code:
My UserControl:
public partial class UserControl1 : UserControl
{
public static readonly DependencyProperty ShowCategoriesProperty =
DependencyProperty.Register("ShowCategories", typeof(bool), typeof(UserControl1), new FrameworkPropertyMetadata(false, OnShowsCategoriesChanged));
public UserControl1()
{
InitializeComponent();
}
public bool ShowCategories
{
get
{
return (bool)GetValue(ShowCategoriesProperty);
}
set
{
SetValue(ShowCategoriesProperty, value);
}
}
private static void OnShowsCategoriesChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
//this callback is only ever hit once when I expect 3 hits...
((UserControl1)d).ShowCategories = (bool)e.NewValue;
}
}
MainWindow.xaml.cs
public partial class MainWindow : Window, INotifyPropertyChanged
{
public MainWindow()
{
InitializeComponent();
Test = true; //this call changes default value of our DP and OnShowsCategoriesChanged is called correctly in my UserControl
Test = false; //these following calls do nothing!! (here is where my issue is)
Test = true;
}
private bool test;
public bool Test
{
get { return test; }
set
{
test = value;
OnPropertyChanged("Test");
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string property)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(property));
}
}
}
MainWindow.xaml
<Window x:Class="Binding.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:uc="clr-namespace:Binding"
DataContext="{Binding RelativeSource={RelativeSource Self}}"
Title="MainWindow" Height="350" Width="525">
<Grid>
<uc:UserControl1 ShowCategories="{Binding Test}" />
</Grid>
</Window>
The problem is that you're setting the ShowCategories value to itself:
((UserControl1)d).ShowCategories = (bool)e.NewValue;
This line is useless, since the value of ShowCategories has already been modified. That's why you're in the property changed callback in the first place. At first glance, this could be seen as a no-op: after all, you're simply setting a property value to its current value, which in WPF doesn't raise any change.
However, since the binding isn't two-way, changing the property value overwrites the binding. That's why there is no more callback raised. Simply remove the assignment in the callback and you're done.
Make the binding mode to be TwoWay.

PropertyChanged event null after setting DataContext

I am setting the DataContext for my View in the View's Constructor to an instance of my ViewModel, just standard stuff. Shortly thereafter, an UPDATE_RECENT_DOCUMENTS_LIST Event fires from the Event Aggregator which my ViewModel catches correctly. A property is changed and the onPropertyChanged method is called, but it fails as the PropertyChanged event is null.
The very next thing I do is an action to the UI which raises a CREATE_PROJECT Event and the same ViewModel is receiving events, except now, the PropertyChanged event is no longer null and everything works as expected.
Is there a specific amount of time that has to pass after setting the DataContext before it registers to the PropertyChanged Event? Is there an event I can wait for that ensures the PropertyChanged event is not null?
Also, I did not run into this problem using standard .NET events, just after integrating Prism and using the very convenient EventAggregator.
I am showing my code behind of the View and the ViewModel, omitting the View XAML for brevity.
ToolBarView.xaml.cs:
namespace ToolBarModule
{
public partial class ToolBarView : UserControl
{
public ToolBarView(ToolBarViewModel toolBarViewModel)
{
InitializeComponent();
this.DataContext = toolBarViewModel;
}
}
}
ToolBarViewModel.cs
namespace ToolBarModule
{
public class ToolBarViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private ToolBarCommands baseCommands;
private IEventAggregator eventAggregator;
private KickStartEvent kickStartEvent;
private SubscriptionToken subscriptionToken;
private ObservableCollection<IDocumentReference> recentDocuments = new ObservableCollection<IDocumentReference>();
private ActionCommand newTest;
private ActionCommand openTest;
private ActionCommand saveTest;
private ActionCommand exitApplication;
public ToolBarViewModel(){}
public ToolBarViewModel(IEventAggregator eventAggregator)
{
this.eventAggregator = eventAggregator;
baseCommands = new ToolBarCommands(eventAggregator);
kickStartEvent = eventAggregator.GetEvent<KickStartEvent>();
subscriptionToken = kickStartEvent.Subscribe(kickStartEventHandler, ThreadOption.UIThread, true, toolBarEventHandlerFilter);
}
public ICommand NewTest
{
get
{
if (newTest == null)
{
newTest = new ActionCommand(baseCommands.NewTestAction);
}
return newTest;
}
}
public ICommand OpenTest
{
get
{
if (openTest == null)
{
openTest = new ActionCommand(baseCommands.OpenTestAction);
}
return openTest;
}
}
public ICommand SaveTest
{
get
{
if (saveTest == null)
{
saveTest = new ActionCommand(baseCommands.SaveTestAction);
}
return saveTest;
}
}
public ICommand ExitApplication
{
get
{
if (exitApplication == null)
{
exitApplication = new ActionCommand(baseCommands.ExitApplicationAction);
}
return exitApplication;
}
}
public ObservableCollection<IDocumentReference> RecentDocuments
{
get
{
return recentDocuments;
}
set
{
recentDocuments = value;
onPropertyChanged("RecentDocuments");
}
}
private void onPropertyChanged(string propertyChanged)
{
if (PropertyChanged != null)
{
PropertyChanged(this,new PropertyChangedEventArgs(propertyChanged));
}
}
private void kickStartEventHandler(KickStartEventsArgs e)
{
switch (e.EventType)
{
case KickStartEventsArgs.KickStartEventType.CREATE_PROJECT:
onPropertyChanged("RecentDocuments");
break;
case KickStartEventsArgs.KickStartEventType.UPDATE_RECENT_DOCUMENTS_LIST:
RecentDocuments.Clear();
foreach (IDocumentReference recentDocs in e.KickStartTestList)
{
RecentDocuments.Add(recentDocs);
}
onPropertyChanged("RecentDocuments");
break;
}
}
}
}
You can also try to set the DataContext of a Grid or an Element below the UserControl. For me it worked.
Example (Doesn't work if you use DependencyProperty):
Code Behind:
public MyUserControl()
{
InitializeComponent();
this.DataContext = new { LabelText = "Hello World!" };
}
XAML
<UserControl x:Class="CoolProject.ViewModel.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"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800">
<Label x:Name="myLabel" Content="{Binding LabelText}"/>
Example 2 (My working code):
Code Behind:
public MyUserControl()
{
InitializeComponent();
this.myGrid.DataContext = new { LabelText = "Hello World!" };
}
XAML
<UserControl x:Class="CoolProject.ViewModel.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"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800">
<Grid x:Name="myGrid">
<Label x:Name="myLabel" Content="{Binding LabelText}"/>
</Grid>
You have to name your UserControl in XAML and use it in binding. Something like following code:
<UserControl x:Name="uc" >
.
.
.
<TextBox Text="{Binding UserName, Mode=TwoWay, ElementName=uc}"/>
Where uc is a name of your UserControl, and Also try to set DataContext when UserControl loaded.
Hope this help.

Data binding a nested property to a listbox

I cannot get any display from my observable collection in a custom object bound to a ListBox. This works fine when I have a string collection in my view model, but no names display when I try to access the property through a custom object. I am not receiving any errors in the output window.
Here is my code:
Custom Object
public class TestObject
{
public ObservableCollection<string> List { get; set; }
public static TestObject GetList()
{
string[] list = new string[] { "Bob", "Bill" };
return new TestObject
{
List = new ObservableCollection<string>(list)
};
}
}
Xaml
<Window x:Class="TestWPF.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">
<Grid>
<ListBox Height="100" HorizontalAlignment="Left" Margin="120,61,0,0" Name="listBox1" VerticalAlignment="Top" Width="120" ItemsSource="{Binding Path=TObj.List}" />
</Grid>
Xaml.cs
public partial class MainWindow : Window
{
private ModelMainWindow model;
public MainWindow()
{
InitializeComponent();
model = new ModelMainWindow();
this.DataContext = model;
this.Loaded += new RoutedEventHandler(MainWindow_Loaded);
}
public void MainWindow_Loaded(object sender, RoutedEventArgs e)
{
this.model.Refresh();
}
}
ViewModel
public class ModelMainWindow : INotifyPropertyChanged
{
private TestObject tObj;
public event PropertyChangedEventHandler PropertyChanged;
public TestObject TObj
{
get
{
return this.tObj;
}
set
{
this.tObj = value;
this.Notify("Names");
}
}
public void Notify(string name)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(name));
}
}
public void Refresh()
{
this.TObj = TestObject.GetList();
}
}
Can't bind to private properties. Also the change notification targets the wrong property, change "Names" to "TObj". (Also i would recommend making the List property get-only (backed by a readonly field), or implementing INoptifyPropertyChanged so the changes cannot get lost)
Your List is private. Make it a public property otherwise WPF can't see it.

Categories