I am trying bind an int to a label in a simple application. The idea is, when a button is pressed, the int, and hence the label, is updated.
I have simplified the code as much as I can but I am not able to see the problem.
I think the issue is with NotifyPropertyChanged(String propertyName) as at runtime start the label's content is updated with the value of the int. However, when the int updates, the label does not.
MainWindow.xaml
<Window x:Class="Press.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"
Title="MainWindow" Height="450" Width="800">
<StackPanel>
<StackPanel Orientation="Horizontal">
<Label Content="Presses: "/>
<Label Content="{Binding Path=PressCount}"/>
</StackPanel>
<Button Content="Press Me" Click="PressMe_Click"/>
</StackPanel>
</Window>
MainWindow.xaml.cs
using System;
using System.Windows;
using System.ComponentModel;
using System.Runtime.CompilerServices;
namespace Press
{
public partial class MainWindow : Window
{
public int pressCount = 0;
public int PressCount {
get {
return pressCount;
}
private set {
if(value != pressCount)
{
pressCount = value;
NotifyPropertyChanged();
}
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged([CallerMemberName] String propertyName = "")
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
public MainWindow()
{
InitializeComponent();
DataContext = this;
}
private void PressMe_Click(object sender, RoutedEventArgs e)
{
PressCount++;
Console.WriteLine(PressCount);
}
}
}
You need MainWindow to implement the interface INotifyPropertyChanged
Related
I would like to ask a question regarding the UI update of a WPF application based on changes applied to MVVM objects stored in a ObservableCollection. But first, let me explain my intuition.
I have the following files created to support my Project Solution. In total there are 5 files, so I present their code for you to replicate the issue. Copy-paste the code below in a new Solution project (WPF - .NET Core) and see for yourself my issue.
File 1: App.xaml
<Application x:Class="WpfAppTestingScenarios.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:WpfAppTestingScenarios"
StartupUri="Window1.xaml">
<Application.Resources>
</Application.Resources>
</Application>
File 2: Window1.xaml
<Window x:Class="WpfAppTestingScenarios.Window1"
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:WpfAppTestingScenarios"
d:DataContext="{d:DesignInstance Type=local:LoginScreenViewModel}"
mc:Ignorable="d"
Title="Window1"
Height="450"
Width="800">
<Grid>
<Button Content="Click me"
Command="{Binding Path=LoginCommand}"
Height="20"
Width="110"/>
</Grid>
</Window>
File 3: MainWindow.xaml
<Window x:Class="WpfAppTestingScenarios.MainWindow"
x:Name="MainWindowName"
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:WpfAppTestingScenarios"
d:DataContext="{d:DesignInstance Type=local:MainWindowViewModel}"
mc:Ignorable="d"
Title="MainWindow"
Height="450"
Width="800">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="160"/>
<ColumnDefinition Width="640"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="225"/>
<RowDefinition Height="210"/>
</Grid.RowDefinitions>
<StackPanel
x:Name="StackPanel1"
Visibility="{Binding StackPanelVisibility1}"
Grid.Row="0"
Grid.Column="1">
<TextBlock Text="Hello World 1"/>
</StackPanel>
<Button
IsEnabled="{Binding Path=EnableViewButton1, UpdateSourceTrigger=PropertyChanged, FallbackValue=false}"
Content="View"
Width="80"
Height="25"
FontSize="10"
FontWeight="Light"
HorizontalAlignment="Right"
VerticalAlignment="Top"
Grid.Row="0"
Grid.Column="1">
</Button>
<StackPanel
x:Name="StackPanel2"
Visibility="{Binding StackPanelVisibility2}"
Grid.Row="1"
Grid.Column="1">
<TextBlock Text="Hello World 2"/>
</StackPanel>
<Button
IsEnabled="{Binding Path=EnableViewButton2}"
Content="View"
Width="80"
Height="25"
FontSize="10"
FontWeight="Light"
HorizontalAlignment="Right"
VerticalAlignment="Top"
Grid.Row="1"
Grid.Column="1">
</Button>
</Grid>
</Window>
File 4: Window1.xaml.cs
using Prism.Commands;
using System.ComponentModel;
using System.Windows;
using System.Windows.Input;
namespace WpfAppTestingScenarios
{
public class LoginScreenViewModel : INotifyPropertyChanged
{
public ICommand LoginCommand
{
get { return new DelegateCommand<object>(FuncLoginCommand); }
}
public void FuncLoginCommand(object parameters)
{
MainWindow WindMain = new MainWindow();
WindMain.Show();
}
public event PropertyChangedEventHandler PropertyChanged;
public void OnPropertyChanged(string property)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(property));
}
}
public partial class Window1 : Window
{
public Window1()
{
InitializeComponent();
DataContext = new LoginScreenViewModel();
}
}
}
File 5: MainWindow.xaml.cs
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Windows;
namespace WpfAppTestingScenarios
{
public class MyCustomClass : INotifyPropertyChanged
{
public string Key { get; set; }
private object _value;
public object Value
{
get { return _value; }
//set { _value = value; NotifyPropertyChanged($"{Value}"); }
//change to
set { _value = value; NotifyPropertyChanged(nameof(Value)); } //still no luck
}
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(string propertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
public class MainWindowViewModel : INotifyPropertyChanged
{
//1. - StackPanelVisibility 1
private Visibility _StackPanelVisibility1;
public Visibility StackPanelVisibility1
{
get
{
return _StackPanelVisibility1;
}
set
{
_StackPanelVisibility1 = value;
OnPropertyChanged("StackPanelVisibility1");
}
}
//2. - StackPanelVisibility 2
private Visibility _StackPanelVisibility2;
public Visibility StackPanelVisibility2
{
get
{
return _StackPanelVisibility2;
}
set
{
_StackPanelVisibility2 = value;
OnPropertyChanged("StackPanelVisibility2");
}
}
//3. - EnableViewButoon 1
private bool _EnableViewButton1;
public bool EnableViewButton1
{
get
{
return _EnableViewButton1;
}
set
{
_EnableViewButton1 = value;
OnPropertyChanged("EnableViewButton1");
}
}
//4. - EnableViewButoon 2
private bool _EnableViewButton2;
public bool EnableViewButton2
{
get
{
return _EnableViewButton2;
}
set
{
_EnableViewButton2 = value;
OnPropertyChanged("EnableViewButton2");
}
}
private void CustomFunction(ObservableCollection<MyCustomClass> UICollection)
{
if ((Visibility)UICollection[0].Value == Visibility.Hidden)
UICollection[0].Value = Visibility.Visible;
if ((bool)UICollection[1].Value == false)
UICollection[1].Value = true;
}
public MainWindowViewModel()
{
ObservableCollection<MyCustomClass> dict = new ObservableCollection<MyCustomClass>
{
new MyCustomClass { Key = "StackPanelVisibility", Value = StackPanelVisibility1 },
new MyCustomClass { Key = "EnableViewButton", Value = EnableViewButton1 }
};
CustomFunction(dict);
}
public event PropertyChangedEventHandler PropertyChanged;
public void OnPropertyChanged(string property)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(property));
}
}
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
DataContext = new MainWindowViewModel();
}
}
}
So even though everything was set successfully and with no errors, when I run the application and I click the button Click me the UI and thus the objects of the MainWindow are not updated.
Initially, I tried this logic with Dictionaries. But then I read that Dictionary cannot update the UI of a WPF application so I changed it to an ObservableCollection. However, both approaches didn't work for me.
Edit
Based on this answer, I created the following code
public class MyCustomClass : INotifyPropertyChanged
{
public string Key { get; set; }
private object _value;
public object Value
{
get { return _value; }
//set { _value = value; NotifyPropertyChanged($"{Value}"); }
//change to
set { _value = value; NotifyPropertyChanged(nameof(Value)); } //still no luck
}
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(string propertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
But still, I don't observe any UI change.
My end result would be to enable the two buttons in the MainWindow like in the screen below (when I click the button Click me)
Image of my desired result
Admittedly I am new to wpf. But i have spent some time Googling about it all and I am stumped.
in essence i want to update my TextBlock in my UI using Binding whenever my Model values change.
So this is my Model:
using System.ComponentModel;
using System.Runtime.CompilerServices;
namespace WpfApplication1
{
public class MyModel : INotifyPropertyChanged
{
protected void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
this.PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
public event PropertyChangedEventHandler PropertyChanged;
protected bool SetProperty<T>(ref T storage, T value, [CallerMemberName] string propertyName = null)
{
if (Equals(storage, value))
{
return false;
}
storage = value;
this.OnPropertyChanged(propertyName);
return true;
}
public string MyField { get; set ; }
}
}
This is my UI:
<Window x:Class="WpfApplication1.MainWindow"
xmlns:viewModels="clr-namespace:WpfApplication1"
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:WpfApplication1"
d:DataContext="{d:DesignInstance viewModels:MyModel, IsDesignTimeCreatable=True}"
mc:Ignorable="d"
Title="MainWindow" Height="350" Width="525">
<Window.DataContext>
<viewModels:MyModel></viewModels:MyModel>
</Window.DataContext>
<Grid>
<StackPanel Orientation="Vertical">
<TextBlock Text="{Binding MyModel.MyField}"></TextBlock>
<Button Content="Click Me!" Click="Button_Click" />
</StackPanel>
</Grid>
</Window>
This is my code behind:
using System.Windows;
namespace WpfApplication1
{
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
public MyModel myModel = new MyModel();
private void Button_Click(object sender, RoutedEventArgs e)
{
myModel.MyField = "has worked";
}
}
}
When i press the button the text does not change on the UI..?
The instance you create in the code behind is not the same as you assign in xaml.
Change the button click event to
private void Button_Click(object sender, RoutedEventArgs e)
{
var model = this.DataContext as MyModel;
model.MyField = "has worked";
}
And the binding in xaml to
<TextBlock Text="{Binding MyField}"></TextBlock>
And in your viewmodel you are not calling the notify property changed. So create a private field and modify the property as below.
private string myField;
public string MyField
{
get { return this.myField; }
set { this.SetProperty(ref this.myField, value); }
}
I have a problem with data binding. A test application that I have looks as follows:
There's a mainwindow:
<Window x:Class="WpfApplication2.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:Controls="clr-namespace:WpfApplication2"
x:Name="main"
Title="MainWindow" >
<StackPanel >
<Button Content="Button" Click="Button_Click"/>
<Controls:UserControl1 />
</StackPanel>
</Window>
And a user control:
<UserControl x:Class="WpfApplication2.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"
x:Name="uc"
>
<Grid >
<TextBox Width="40" Text="{Binding ElementName=main,
Path=Status, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"/>
</Grid>
</UserControl>
I want to click the button on the main window to have the value of text box in user control updated:
The code of MainWindow file:
namespace WpfApplication2
{
public partial class MainWindow : Window, INotifyPropertyChanged
{
public MainWindow()
{
InitializeComponent();
}
public event PropertyChangedEventHandler PropertyChanged;
private void RaisePropertyChanged(string propertyName)
{
if (PropertyChanged != null) PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
private string _status;
public string Status
{
get { return _status; }
set
{
if (value != _status)
{
_status = value;
RaisePropertyChanged("Status");
}
}
}
private void Button_Click(object sender, RoutedEventArgs e)
{
if (Status == "one")
Status = "two";
else
Status = "one";
}
}
}
And the code of UserControl:
namespace WpfApplication2
{
public partial class UserControl1 : UserControl, INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private void RaisePropertyChanged(string propertyName)
{
if (PropertyChanged != null) PropertyChanged(this, e: new PropertyChangedEventArgs(propertyName));
}
public UserControl1()
{
InitializeComponent();
}
}
}
I don't understand why doesn't it work, but the PropertyChanged is always null. The example is in the simplest form I can imagine...
You are trying to access the parent window using the ElementName binding, as far as I am aware, that is not possible. You can however use a relative source binding to get the parent window:
<TextBlock Text="{Binding RelativeSource={RelativeSource AncestorType=Window}, Path=Status}" ... />
Follow up edit:
Your child user control should look like this:
<UserControl
...
x:Name="usr">
<Grid>
<TextBlock Text="{Binding Message, ElementName=usr}"/>
</Grid>
</UserControl>
You will then need to create a dependency property called 'Message' (This is just an example, I'm not sure what you want to call this property).
public partial class YourUserControl: UserControl
{
public string Message
{
get { return (string)GetValue(MessageProperty); }
set { SetValue(MessageProperty, value); }
}
// Using a DependencyProperty as the backing store for Message. This enables animation, styling, binding, etc...
public static readonly DependencyProperty MessageProperty =
DependencyProperty.Register("Message", typeof(string), typeof(YourUserControl), new PropertyMetadata(""));
public UserControl1()
{
InitializeComponent();
}
}
Then, when you declare this in your parent user control, simply set the binding of the Message property to whatever property you need to bind to in your parent user control:
<YourNamespace:YourUserControl Message="{Binding PropertyName, ElementName=elementName}" />
First, this is a simplified version from a wizard control using MVVM. The problem is just easier to reproduce as described below
After much narrowing down, I have resolved an infinite exception in my code to be due to the WPF ContentControl. However, I have yet to figure out how to handle it, other than try-catch wrapping all of my possible instantiation code. Here is sample code that reproduces this...any help on how to keep this infinite exception from occurring would be greatly appreciated.
Additional Details
To sum up, the problem is that if the content control changes its contents, and the thing being loaded in throws an exception, then it will throw, then retry the load, causing the throw again and again.
MainWindow.xaml
<Window x:Class="WpfApplication8.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" Name ="Main">
<Grid>
<ContentControl Name="bar" Content="{Binding ElementName=Main, Path=foo}"/>
<Button Click="ButtonBase_OnClick" Margin="20" Width="50"/>
</Grid>
</Window>
MainWindow.xaml.cs
public partial class MainWindow : Window, INotifyPropertyChanged
{
private UserControl _foo;
public UserControl foo
{
get { return _foo; }
set { _foo = value; OnPropertyChanged("foo"); }
}
public MainWindow()
{
InitializeComponent();
foo = new UserControl1();
}
private void ButtonBase_OnClick(object sender, RoutedEventArgs e)
{
foo = new UserControl2();
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string propertyName)
{
var handler = PropertyChanged;
if (handler != null)
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
UserControl1 is blank and all default
UserControl2.xaml.cs
public UserControl2()
{
InitializeComponent();
throw new Exception();
}
Do not bind ContentControl to MainWindow. Instead use DataTemplates to select the content for the MainWindow. One example-contrived way of doing it is to bind the ContentControl's Content to the DataContext of the MainWindow.
First some observable test data is needed. The specifics of this data are not important. The main point is to have two different classes of test data from which to choose - TestData.cs:
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
namespace fwWpfDataTemplate
{
// Classes to fill TestData
public abstract class Person : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string propertyName)
{
if (PropertyChanged != null) PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
string _name;
public string Name
{
get { return _name; }
set
{
_name = value;
OnPropertyChanged("Name");
}
}
}
public class Student : Person { }
public class Employee : Person
{
float _salary;
public float Salary
{
get { return _salary; }
set
{
_salary = value;
OnPropertyChanged("Salary");
}
}
}
public class TestData : ObservableCollection<Person>
{
public TestData()
: base(new List<Person>()
{
new Student { Name = "Arnold" },
new Employee { Name = "Don", Salary = 100000.0f }
}) { }
}
}
Then add DataTemplates to MainWindow's resources - MainWindow.xaml:
<Window x:Class="fwWpfDataTemplate.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:me="clr-namespace:fwWpfDataTemplate"
Title="MainWindow" Height="350" Width="525">
<Window.Resources>
<DataTemplate DataType="{x:Type me:Student}">
<StackPanel>
<TextBlock Text="Student"/>
<TextBlock Text="{Binding Name}"/>
</StackPanel>
</DataTemplate>
<DataTemplate DataType="{x:Type me:Employee}">
<StackPanel>
<TextBlock Text="Employee"/>
<TextBlock Text="{Binding Name}"/>
<TextBlock Text="Salary"/>
<TextBlock Text="{Binding Salary}"/>
</StackPanel>
</DataTemplate>
</Window.Resources>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition/>
</Grid.RowDefinitions>
<Button Content="Change Data Context" Click="Button_Click" />
<ContentControl Grid.Row="1" Content="{Binding}"/>
</Grid>
</Window>
Note: instead of the StackPanels the contents of the DataTemplates could be UserControl1, UserControl2, etc.
Then add some code to change the data context - MainWindow.cs:
using System.Windows;
namespace fwWpfDataTemplate
{
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
TestData testData = new TestData();
int testIndex = -1;
private void Button_Click(object sender, RoutedEventArgs e)
{
testIndex = (testIndex + 1) % testData.Count;
this.DataContext = testData[testIndex];
}
}
}
Enjoy.
I want to bind two controls like font size slider and text box, and each control is on different window on wpf, so how can I bind them?
Here's an example of how to do it:
1) Create a WPF project.
2) Change the contents of the MainWindow.xaml to the following (don't forget to correct the namespaces in all the code that I'm posting, for example in my code the namespace is WpfApplication2):
<Window x:Class="WpfApplication2.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">
<StackPanel>
<Button Content="Settings Window" Click="SettingsWindowButton_OnClick"/>
<Button Content="Bound Window" Click="BoundWindowButton_OnClick"/>
</StackPanel>
</Window>
3) Change the contents of the MainWindow.xaml.cs to the following:
namespace WpfApplication2
{
using System.Windows;
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
ViewModel viewModel = new ViewModel();
public MainWindow()
{
InitializeComponent();
}
private void SettingsWindowButton_OnClick(object sender, RoutedEventArgs e)
{
var settingsWindow = new SettingsWindow();
settingsWindow.DataContext = viewModel;
settingsWindow.Show();
}
private void BoundWindowButton_OnClick(object sender, RoutedEventArgs e)
{
var boundWindow = new BoundWindow();
boundWindow.DataContext = viewModel;
boundWindow.Show();
}
}
}
4) Create a class named ViewModel in your project with the following code:
namespace WpfApplication2
{
using System.ComponentModel;
public class ViewModel : INotifyPropertyChanged
{
private int _fontSizeSetting = 10;
public event PropertyChangedEventHandler PropertyChanged;
public int FontSizeSetting
{
get { return _fontSizeSetting; }
set
{
_fontSizeSetting = value;
OnPropertyChanged("FontSizeSetting");
}
}
protected virtual void OnPropertyChanged(string propertyName)
{
var handler = PropertyChanged;
if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
}
}
}
5) Add two new Windows to your project named BoundWindow and SettingsWindow with the following markup:
<Window x:Class="WpfApplication2.BoundWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="BoundWindow" Height="300" Width="300">
<Grid>
<TextBox FontSize="{Binding FontSizeSetting, Mode=TwoWay}" Text="test..."/>
</Grid>
</Window>
-
<Window x:Class="WpfApplication2.SettingsWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="SettingsWindow" Height="300" Width="300">
<Grid>
<Slider Value="{Binding FontSizeSetting, Mode=TwoWay}" Minimum="10" Maximum="100"/>
</Grid>
</Window>
Now everything should be working as expected. What you basically did was to create a view model to set as the DataContext of your Windows. They both bind to the FontSizeSetting property of your view model, and when you change it in one window, WPF binding system takes care of changing the other value automatically.