C# WPF binding doesn't take data from Property - c#

In my XAML I am doing the following
<Label Content="{Binding ElementName=Root, Path=UserData.Email, Mode=OneWay}" />
the Root element is my Window itself and the UserData Is a get; private set; auto property in my codebehind file, the Email property is get-only and is of type string.
the UserData object gets set after the user has logged in. But the binding is not taking the value from the object. I have verified that the object does indeed contain the correct data and isn't null. What am I missing here?

I went ahead and created a hello world version for this. Here is the xml. This should simply change the banner when the button is clicked to the text in the text box. I couldn't find a super simple example so I just made one. Obviously there are way more advanced ways to do this but it should make for a simple version to build from.
<Window x:Class="Hello_World.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>
<Label Name="MyLabel" Content="{Binding MyLabel}" HorizontalAlignment="Left" Margin="58,37,0,0" VerticalAlignment="Top" Height="65" Width="423" FontSize="44"/>
<TextBox Name="MyTextBox" HorizontalAlignment="Left" Height="28" Margin="163,162,0,0" TextWrapping="Wrap" Text="TextBox" VerticalAlignment="Top" Width="163"/>
<Button Content="Change Banner" HorizontalAlignment="Left" Margin="251,209,0,0" VerticalAlignment="Top" Width="109" Click="Button_Click"/>
</Grid>
</Window>
Next is the ModelView that implements the INotifyPropertyChanged interface. Note that your properties must be public properties with a getter, setter and backing field. This allows you to call the OnPropetyChanged() method whenever the property is set.
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Hello_World
{
public class MainViewModel: INotifyPropertyChanged
{
private string _myLabel;
public string MyLabel
{
get { return _myLabel; }
set
{
_myLabel = value;
OnPropertyChanged(nameof(MyLabel));
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string propetyName)
{
if(PropertyChanged != null)
PropertyChanged(this,new PropertyChangedEventArgs(propetyName));
}
}
}
Lastly the MainWindow. Set the DataContext in the main constructor. Note I could have set the DataContext of the main grid and all of its children would inherit the same DataContext. This would keep you from having to set all of the components' individually.
namespace Hello_World
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
private MainViewModel MyViewModel;
public MainWindow()
{
InitializeComponent();
MyViewModel = new MainViewModel();
// Here's where I'm setting the object to look at.
DataContext = MyViewModel;
// Now I don't need to access the textbox directly.
MyViewModel.MyLabel = "Hello World";
}
private void Button_Click(object sender, RoutedEventArgs e)
{
// Note: ICommand is a more advanced topic.
MyViewModel.MyLabel = MyTextBox.Text;
}
}
}

Related

CanExecute method not firing

I'm using WinUI MVVM (as an MVVM newbie)
Here is my button in XAML
<Button Grid.Row="0" Content="Create New" Width="100" Margin="5"
Command="{x:Bind ViewModel.CreateNewCommand }"
Visibility="{x:Bind ViewModel.IsCreateNewVisible, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />
In the constructor of the ViewModel I have this connection:
CreateNewCommand = new RelayCommand(Handle_CreateNewCommand, CanExecuteCreateNew);
and here is the CanExecute method:
public bool CanExecuteCreateNew()
{
return IsCreateNewEnabled;
}
private bool _isCreateNewEnabled = false;
public bool IsCreateNewEnabled
{
get => _isCreateNewEnabled;
set
{
SetProperty(ref _isCreateNewEnabled, value);
}
}
If I assign the IsCreateNewEnabled property in the VM constructor it renders correctly, enabled or disabled.
When I click the button it fires the handler method and before a single line of code in that method is executed, it fires the canExecute method with a value of true. In the handler method I set IsCreateNewEnabled = false but that has no effect on the button and doesn't fire the CanExecute method.
Any ideas?
Thanks
Carl
You can do it this way with the CommunityToolkit.Mvvm NuGet package.
ViewModel.cs
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
namespace Mvvm;
// This class needs to be "partial" for the CommunityToolkit.Mvvm.
public partial class YourViewModel : ObservableObject
{
[RelayCommand(CanExecute = nameof(CanCreateNew))]
// A "CreateNewCommand" command will be auto-generated.
private void CreateNew()
{
}
[ObservableProperty]
[NotifyCanExecuteChangedFor(nameof(CreateNewCommand))]
// A "CanCreateNew" property that
// notifies the "CreateNewCommand" to update its availability
// will be auto-generated.
private bool canCreateNew = false;
}
MainWindow.xaml.cs
using Microsoft.UI.Xaml;
namespace Mvvm;
public sealed partial class MainWindow : Window
{
public MainWindow()
{
this.InitializeComponent();
}
public YourViewModel ViewModel { get; } = new();
}
MainWindow.xaml
<Window
x:Class="Mvvm.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"
mc:Ignorable="d">
<StackPanel>
<Button
Command="{x:Bind ViewModel.CreateNewCommand}"
Content="Create New" />
<ToggleButton
Content="Can create new"
IsChecked="{x:Bind ViewModel.CanCreateNew, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />
</StackPanel>
</Window>

can't get a label to update from wpf in another class

I'm making a basic program where a label updates when the user types in a text box. i'm trying to use data binding and INotifyPropertyChanged to work this out, so i don't want any workarounds. i used 2 buttons so i can actually see if they updated. here's my main class
namespace TestStringChangeFromAnotherClass
public partial class MainWindow : Window
{
textClass someTextClass = new textClass();
public MainWindow()
{
InitializeComponent();
}
public string someString1;
public string someString2;
private void btn1_Click(object sender, RoutedEventArgs e)
{
someTextClass.Text1 = tbx1.Text;
}
private void btn2_Click(object sender, RoutedEventArgs e)
{
someTextClass.Text2 = tbx1.Text;
}
}
here's the wpf for it
<Window x:Class="TestStringChangeFromAnotherClass.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
DataContext="{Binding RelativeSource={RelativeSource Self}}"
Title="MainWindow" Height="350" Width="525">
<Grid>
<Button x:Name="btn1" Content="Button" HorizontalAlignment="Left" Height="36" Margin="29,246,0,0" VerticalAlignment="Top" Width="108" Click="btn1_Click"/>
<Button x:Name="btn2" Content="Button" HorizontalAlignment="Left" Height="36" Margin="227,246,0,0" VerticalAlignment="Top" Width="124" Click="btn2_Click"/>
<Label x:Name="lbl1" Content="{Binding textClass.Text1}" HorizontalAlignment="Left" Height="37" Margin="74,32,0,0" VerticalAlignment="Top" Width="153"/>
<Label x:Name="lbl2" Content="{Binding textClass.Text2, UpdateSourceTrigger=PropertyChanged}" HorizontalAlignment="Left" Height="38" Margin="74,90,0,0" VerticalAlignment="Top" Width="153"/>
<TextBox x:Name="tbx1" HorizontalAlignment="Left" Height="37" Margin="290,32,0,0" TextWrapping="Wrap" Text="TextBox" VerticalAlignment="Top" Width="190"/>
</Grid>
as you can see, i've tried using UpdateSourceTrigger. i've also tried to use "someTestClass.Text1" instead of textClass.Test1, because that's how i defined it in the MainWindow. Here's my textClass
namespace TestStringChangeFromAnotherClass
public class textClass:INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private string text1;
public string Text1
{
get { return text1; }
set
{
text1 = value;
NotifyPropertyChanged("Text1");
}
}
private string text2;
public string Text2
{
get { return text2; }
set
{
text2 = value;
NotifyPropertyChanged("Text2");
}
}
protected void NotifyPropertyChanged(string info)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(info));
}
}
i can't figure out how to get wpf to look for the Test1 or Test2 strings in the separate class and update them when the strings change. i have a feeling the problem lies within DataContext, but i can't figure it out. i'd also rather not use DataContext within c#, only in WPF
UPDATE:
when i debug this, when it gets to NotifyPropertyChanged, PropertyChanged is evaluated as null. could that be the problem?
You bind DataContext to your Window which, as far as I can see, doesn't have textClass property. It has someTextClass field of textClass type. In order for your code to work your can change someTextClass to public property:
public textClass someTextClass { get; private set; }
initialize it in constructor:
public MainWindow()
{
someTextClass = new textClass();
InitializeComponent();
}
and then change binding to point to someTextClass property
<Label x:Name="lbl1" Content="{Binding someTextClass.Text1}" .../>
<Label x:Name="lbl2" Content="{Binding someTextClass.Text2}" .../>
You are binding to the MainWindow class itself as your DataContext, and trying to access the property called someTextClass that has the properties you want to bind to.
You are running into two problems:
1) Your XAML is trying to reference the desired object by it's type, not it's name. Not going to work. Your binding expressions should look like {Binding someTextClass.Text1} (note the difference in the first part of the path expression).
2) You can only bind to public things. Your field is not defined as public, and therefore is private. Even though the XAML should logically "be able to see" the property, as it's the same class, DataBinding will only work on public properties.
3) EDIT: You must also make this a property. WPF will not bind to fields.
In general, using Snoop will help diagnose silent binding errors.

Bind Data From Property to Textblock - MVVM Light and WPF

I have a textblock in WPF which is bound to a property in my ViewModel class. On click of a button I wish to modify the property and expect the same to be reflected in my textblock. I want all these to be done purely using MVVM (MVVMLight). I am using MMVM light and VS 2012.
Challenges- On button click the changes are not being reflected. Though the program execution is going inside the property , changes are not being made.
Please Help !!
Program- View:
<Window x:Class="MvvmLight1_Trail.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:ignore="http://www.ignore.com"
mc:Ignorable="d ignore"
Height="500"
Width="500"
Title="MVVM Light Application"
DataContext="{Binding Main, Source={StaticResource Locator}}">
<Grid x:Name="LayoutRoot">
<TextBlock FontSize="34"
Text="{Binding Path=MyText,UpdateSourceTrigger=Default, Mode=TwoWay}"
VerticalAlignment="Center"
HorizontalAlignment="Center"
TextWrapping="Wrap" />
<Button Width="100" Height="100" Command="{Binding PressCommand}" Margin="198.985,277.537,193.014,92.462" Content="Press Me"/>
</Grid>
View Model
using GalaSoft.MvvmLight;
using GalaSoft.MvvmLight.Command;
using MvvmLight1_Trail.Model;
using System.ComponentModel;
using System.Threading;
namespace MvvmLight1_Trail.ViewModel
{
public class MainViewModel : ViewModelBase
{
public RelayCommand PressCommand { get; private set; }
Thread t;
private string _welcomeTitle = string.Empty;
public string MyText
{
get
{
return _welcomeTitle;
}
set
{
if (_welcomeTitle == value)
{
return;
}
_welcomeTitle = value;
RaisePropertyChanged(MyText);
}
}
/// <summary>
/// Initializes a new instance of the MainViewModel class.
/// </summary>
public MainViewModel()
{
PressCommand = new RelayCommand(() => MyFunc());
myfunc();
}
private void MyFunc()
{
this.MyText = "Hi2";
}
private void myfunc()
{
this.MyText = "Hello";
this.MyText = "Hi";
}
}
}
Replace
RaisePropertyChanged(MyText);
to
RaisePropertyChanged("MyText");
PropertyChanged event should be raised on property name and not on property value.
Already answered by #Rohit Vats. You can also call RaisePropertyChanged like, RaisePropertyChanged( () => MyText) to ease renaming later.
Late to the game but:
in new C# 6 you can also use nameof like this:
RaisePropertyChanged(nameof(MyText))

How to properly set up a WPF UserControl for Binding

I want to create a UserControl, that essentially is a Label with a TextBox. Now I want to be able to Bind TextBox.Text to different values.
For this I created a DependencyProperty in my UserControl and am now trying to bind something to that newly created DependencyProperty, but the Text seems not to get updated.
My UserControl1.xaml looks like this:
<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="48" d:DesignWidth="200">
<Grid>
<WrapPanel Height="48" HorizontalAlignment="Left" Name="wrapPanel1" VerticalAlignment="Top" Width="200">
<Label Content="Label" Height="48" Name="label1" Width="100" />
<TextBox Height="48" Name="textBox1" Width="100" />
</WrapPanel>
</Grid>
And my UserControl1.xaml.cs looks like this:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using System.Diagnostics;
namespace WpfApplication1
{
/// <summary>
/// Interaction logic for UserControl1.xaml
/// </summary>
public partial class UserControl1 : UserControl
{
private string value;
public string Value
{
get { return value; }
set
{
this.value = value;
textBox1.Text = value;
Trace.TraceInformation("value set in UserControl1");
}
}
public static readonly DependencyProperty ValueProperty =
DependencyProperty.Register("Value", typeof(string), typeof(UserControl1));
public UserControl1()
{
InitializeComponent();
}
}
}
I am using the UserControl like this:
<my:UserControl1 x:Name="userControl11" Value="{Binding Path=Name}" />
with DataContext set to an object that has a Name property and implements INotifyPropertyChanged for this property.
You put connection between TextBox's Text and UserControl's Value in wrong place. The CLR property is used for convenience but it is not used by Bind Engine. You need bind TextBox's Text to Usercontrol's Value explicitly on XAML or code behind, such as (assuming you give your user control a name called root):
<TextBox x:Name="textBox1" Text="{Binding Path=Value, ElementName=root}"/>
You cannot add additional logic or code to the get or set accessor for a property that wraps a dependency property. It will not be executed.
The reason for this, is because the WPF designer will actually generate code to use the DependencyProperty directly. The get/set property is there just for convenience if you use it in the code. Because you want the DependencyProperty and the property's get/set to do the same thing, you should only call GetValue and SetValue in the get/set accessors while passing in the related dependency property.
See these tutorials:
Dependency Properties
Dependency Properties Overview
Take a look at this implementation. It uses a very simple MVVM design to obtain databinding.
Basically this is what it's doing:
You have your UserControl (the View) that sets it's DataContext to a corresponding ViewModel.
The TextBox on the View is then bound to a public property in that ViewModel
By implementing INotifyPropertyChanged, you are enabling your ViewModel to effectively update the UI anytime it changes the value of the Text property.
This can repeated for any number of bindings and applied to many different types such as Lists, ObservableCollections, integers.
View
<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="48" d:DesignWidth="200">
<Grid>
<WrapPanel Height="48" HorizontalAlignment="Left" Name="wrapPanel1" VerticalAlignment="Top" Width="200">
<Label Content="Label" Height="48" Name="label1" Width="100" />
<TextBox Height="48" Name="textBox1" Width="100" Text={Binding Text} />
</WrapPanel>
</Grid>
View Code-Behind
namespace WpfApplication1
{
using System.Windows;
using System.Windows.Controls;
public partial class UserControl1: UserControl
{
public UserControl1()
{
DataContext = new UserControl1ViewModel();
InitializeComponent();
}
}
}
ViewModel
namespace WpfApplication1
{
using System.Collections.ObjectModel;
using System.Windows.Controls;
class UserControl1ViewModel : INotifyPropertyChanged
{
// Ultimately, this field (text) would be moved to a model along with
// any other data fields for this view but for the sake of space,
// I've put it in the ViewModel.
private string text = "";
public string Text
{
get { return text; }
set
{
text = value;
RaisePropertyChanged("Text");
}
}
public MainWindowViewModel()
{
Text = "Hello!";
}
// This is the implementation of INotifyPropertyChanged that is used
// to inform the UI of changes.
public event PropertyChangedEventHandler PropertyChanged;
protected void RaisePropertyChanged(string propertyName)
{
var handler = PropertyChanged;
if (handler != null)
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
}
Good luck!

Binding data items on second window

Finally I decided to jump on the WPF bandwagon and decided to follow the MVVM Pattern to create my applications. I am also using Caliburn.Micro.
I found many examples of Binding data to Windows but all the examples contained just one MainWindow. I couldn't figure out how to reference and make the binding when opening a second and third window. To illustrate my problem I created a simple application. This application has two windows, the main one named ShellView and the second one named Window1View. All I need in this application is to display the content of myStr1 into the TextBox on Window1View.
Here is the code:
Views.ShellView.xaml
<Window x:Class="Test.Views.ShellView"
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>
<TextBlock x:Name="Title" />
<Button Content="Window 1" Height="31" HorizontalAlignment="Left" Margin="24,268,0,0" Name="btnWin1" VerticalAlignment="Top" Width="87" Click="btnWin1_click" />
</Grid>
</Window>
Views.ShellView.xaml.cs
namespace Test.Views
{
using System.Windows;
public partial class ShellView : Window
{
public ShellView()
{
InitializeComponent();
}
private void btnWin1_click(object sender, RoutedEventArgs e)
{
Window1View win1 = new Window1View();
win1.Show();
}
}
}
ViewModels.ShellViewModel.cs
namespace Test.ViewModels
{
using Caliburn.Micro;
public class ShellViewModel : PropertyChangedBase
{
public static string txt1 = "String 1";
public static string txt2 = "String 2";
private string title;
public string Title
{
get { return title; }
set
{
if (title != value)
{
title = value;
RaisePropertyChangedEventImmediately("Title");
}
}
}
public ShellViewModel()
{
Title = "Hello Caliburn.Micro";
}
}
}
Views.Window1View.xaml
<Window x:Class="Test.Views.Window1View"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:c="clr-namespace:Caliburn.Micro;assembly=Caliburn.Micro"
Title="Window 1" Height="300" Width="300">
<Grid>
<Label Content="TextBox 1" Height="26" HorizontalAlignment="Left" Margin="12,40,0,0" Name="label1" VerticalAlignment="Top" Width="75" />
<TextBox Height="29" HorizontalAlignment="Left" Margin="106,39,0,0" Name="txtBox1" VerticalAlignment="Top" Width="145" Text="{Binding myStr1}" />
</Grid>
</Window>
View.Window1View.xaml.cs
using System.Windows;
namespace Test.Views
{
/// <summary>
/// Interaction logic for Window1View.xaml
/// </summary>
public partial class Window1View : Window
{
public Window1View()
{
InitializeComponent();
}
}
}
ViewModels.Window1ViewModel.cs
namespace Test.ViewModels
{
class Window1ViewModel
{
public Window1ViewModel()
{
myStr1 = ShellViewModel.txt1;
}
public string myStr1 { get; set; }
}
}
Bootstrapper.cs
namespace Test
{
public class Bootstrapper : Caliburn.Micro.Bootstrapper<Test.ViewModels.ShellViewModel>
{
}
}
App.xaml
<Application
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
x:Class="Test.App">
<Application.Resources>
</Application.Resources>
</Application>
App.xaml.cs
namespace Test
{
using System.Windows;
public partial class App : Application
{
Bootstrapper bootstrapper;
public App()
{
bootstrapper = new Bootstrapper();
}
}
}
Any help with this would be greatly appreciated.
Thanks
Carmelo
Maybe I'm missing something because I'm not familiar with Caliburn.Micro, but I see a couple of things going on here.
First, I don't see anywhere that you're setting the DataContext of the Window1View to be an instance of Window1ViewModel.
Second, your Window1ViewModel doesn't derive from PropertyChangedBase and you don't call RaisePropertyChanged when you change the myStr1 property.
A common way to implement view model communication in MVVM is to use the Mediator Pattern. Most MVVM frameworks include a 'Messenger' class that allows you to decouple your design by publishing and subscribing to events. In Caliburn Micro, mediation is supported by the EventAggregator class.
Since you are new to MVVM, I would also recommend the following resources:
Implementing the MVVM Pattern
Advanced MVVM Scenarios
User Interaction Patterns
A few things here:
You are using code behind unnecessarily. Ideally you should aim for virtually no code behind when using MVVM. Instead of creating a btnWin1_click handler, name the button and implement a method on your view model with the same name. Caliburn.Micro will invoke the view model method based on convention.
Use x:Name rather than Name
When displaying the Window1ViewModel, you are not using Caliburn.Micro to invoke the window display. This means that no binding exists between your Window1View and Window1ViewModel. If this is a separate window, use the WindowManager type from Caliburn.Micro. Instantiate your Window1ViewModel, and use the WindowManager class to display it. Caliburn.Micro will locate the appropriate view based on conventions, and bind the view to your view model.
As mentioned, rather than reference the ShellViewModel directly in the Window1ViewModel (which couples the view models and makes Window1ViewModel less reuseable), use the mediator pattern. Caliburn.Micro comes with an EventAggregator class - you can publish the change in text from the ShellViewModel, and subscribe to the event in your Window1ViewModel.

Categories

Resources