I'm learning WPF MVVM and I get stuck with one problem. I'm calculating variable value in one class every 200ms, then I'm sending it to ViewModel where PropertyChanged event is fired, but the value did not appear on View. When I copy this code for calculation of variable value to ViewModel constructor then everything is working. Could you give me some hints what am I doing wrong?
Class where I calculate values:
public class CalculatingSecondsTask
{
public string seconds { get; set; }
public CalculatingSecondsTask()
{
Task.Run(async () =>
{
int i = 0;
while (true)
{
await Task.Delay(200);
seconds = (i++).ToString();
UpdateSecondsInViewModel(seconds);
}
});
}
public void UpdateSecondsInViewModel(string secs)
{
ViewModel test = new ViewModel();
test.Seconds_To_Start = secs;
}
My ViewModel:
public class ViewModel : INotifyPropertyChanged
{
private string mSeconds_To_Start;
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string propertyName)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (propertyName == null)
throw new ArgumentNullException("propertyExpression");
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
public string Seconds_To_Start
{
get
{
return mSeconds_To_Start;
}
set
{
mSeconds_To_Start = value;
OnPropertyChanged(nameof(Seconds_To_Start));
}
}
And my View is like:
<Window x:Class="Program.Views.StartWindow"
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:Program.Views"
xmlns:ViewModel="clr-namespace:Program.ViewModels"
mc:Ignorable="d"
Title="Program" Height="110" Width="400">
<Window.DataContext>
<ViewModel:ViewModel/>
</Window.DataContext>
<Grid>
<!-- Text -->
<TextBlock x:Name="Seconds_To_Start" HorizontalAlignment="Center">
<Run Text="{Binding Seconds_To_Start}" />
<Run Text="seconds" />
</TextBlock>
</Grid>
</Window>
Related
I have a problem to bind a subclass to my XML textbox, I followed this post to do it, but it does not work without using a static class. Is there a way to do without using a static class?
I followed this post as reference.
Binding textbox values to a model in wpf
My code is:
public class Model:INotifyPropertyChanged{
public event PropertyChangedEventHandler PropertyChanged;
private string title;
public string Title{
get {
return title;
}
set {
if (tilte!= value) {
tilte= value;
NotifyPropertyChanged();
}
}
}
private void NotifyPropertyChanged([CallerMemberName] string propertyName = "") {
PropertyChangedEventHandler handler = PropertyChanged;
if (null != handler) {
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
}
public class ViewModel{
public Model modelObj;
public ViewModel(){
modelObj= new Model();
this.DataContext = modelObj;
modelObj.Title = "New title"; // <--- this don't update xml
}
}
<Page
x:Class="AppTest.Demo"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:AppTest"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
xmlns:m ="using:Models"
xmlns:vm ="using:ViewModels"
Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
<Page.DataContext>
<m:Model></m:Model>
</Page.DataContext>
<Grid>
<TextBlock Text="{Binding Path=Title}"/>
</Grid>
</Page>
You could set your view model as the data context and bind to Model.Title.
Update
This works:
<Page x:Class="WpfApplication8.Page1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:AppTest"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
xmlns:m ="using:Models"
xmlns:vm="clr-namespace:WpfApplication8.ViewModels">
<Page.DataContext>
<vm:ViewModel/>
</Page.DataContext>
<Grid>
<TextBlock Text="{Binding ModelObj.Title, TargetNullValue='null', FallbackValue='fallback'}"/>
</Grid>
public abstract class BindableBase : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected void NotifyPropertyChanged([CallerMemberName] string propertyName = "")
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
public class Model : BindableBase
{
private string title;
public string Title
{
get
{
return title;
}
set
{
if (title != value)
{
title = value;
NotifyPropertyChanged();
}
}
}
}
public class ViewModel : BindableBase
{
private Model modelObj;
public Model ModelObj
{
get
{
return modelObj;
}
set
{
modelObj = value;
NotifyPropertyChanged();
}
}
public ViewModel()
{
ModelObj = new Model();
ModelObj.Title = "New title";
}
}
You should set the Title property of the instance of the Model class that you set as the DataContext of the Page:
<Page.DataContext>
<m:Model Title="New title"></m:Model>
</Page.DataContext>
Or:
<Page.DataContext>
<m:ViewModel />
</Page.DataContext>
<Grid>
<TextBlock Text="{Binding Path=modelObj.Title}"/>
</Grid>
Also, you don't set the DataContext property of a view model. You set the DataContext property of a view to an instance of a view model.
Edit:
modelObj must be a public property (and not a field) in order for you to be able to bind to it:
public Model modelObj { get; set; }
I'm trying to Bind the Text property of the TextBlock to StaticClass.Percent. Since I couldn't do that (Or could I?) I have defined the LoadingPercent in my ViewModel so that I can bind to it. It is still not working. How can I solve this? Or can I bind to the StaticClass directly and ignore the ViewModel approach?
<Window x:Class="TestBinding.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:testBinding="clr-namespace:TestBinding"
Title="MainWindow" Height="350" Width="525">
<Window.DataContext>
<testBinding:ViewModel/>
</Window.DataContext>
<StackPanel>
<TextBlock Width="100"
HorizontalAlignment="Center"
Text="{Binding LoadingPercent}"/>
<Button Content="Change"
Width="200"
Height="30"
Margin="0 20 0 0"
HorizontalAlignment="Center"
Click="ChangeText"/>
</StackPanel>
</Window>
public partial class MainWindow
{
public MainWindow()
{
InitializeComponent();
}
private void ChangeText(object sender, RoutedEventArgs e)
{
StaticClass.Percentage = 10;
}
}
public class ViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private double loadingPercent;
public double LoadingPercent
{
get { return StaticClass.Percentage; }
set
{
loadingPercent = value;
RaisePropertyChanged("LoadingPercent");
}
}
private void RaisePropertyChanged(string propertyName)
{
var handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
}
public static class StaticClass
{
public static int Percentage { get; set; }
}
Here is a mistake:
private double loadingPercent;
public double LoadingPercent
{
get { return StaticClass.Percentage; }
set
{
loadingPercent = value;
RaisePropertyChanged("LoadingPercent");
}
}
You return in get the StaticClass.Percentage but you assign loadingPercent in set.
I am not sure why you need the static class after all, but if you want to ditch the viewmodel and bind directly to the static property, see here
I'm trying to validate the text in a textbox when a key is pressed. Here's the shortest code sample that shows what I'm trying to do:
<Window x:Class="WpfApplication1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525">
<Grid>
<TextBox FontSize="15" HorizontalAlignment="Left" Name="txtEmail" VerticalAlignment="Top" Width="135"
Text="{Binding ValidationRules.EmailAddress, ValidatesOnExceptions=True, UpdateSourceTrigger=PropertyChanged}"/>
</Grid>
</Window>
"ValidationRules" class:
class ValidationRules
{
string email = "";
public string EmailAddress
{
get
{
return email;
}
set
{
Console.WriteLine("Setting!");
//Only check if there is any text for now...
if (String.IsNullOrWhiteSpace(value))
{
throw new Exception();
}
email = value;
}
}
}
When I start typing in the textbox, I don't get "Setting" as console output, even though I'm using UpdateSourceTrigger=PropertyChanged. I've done my research but all the examples I could find are long winding and confusing. I would also appreciate it if you could point out any other mistakes I have in validation, but try to explain in simple terms if possible because I'm new to WPF.
It must be an issue on where you are setting your DataContext.
This example seems to work fine:
Code:
public partial class MainWindow : Window, INotifyPropertyChanged
{
public MainWindow()
{
InitializeComponent();
ValidationRules = new ValidationRules();
}
private ValidationRules _validation;
public ValidationRules ValidationRules
{
get { return _validation; }
set { _validation = value; NotifyPropertyChanged("ValidationRules"); }
}
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(string property)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(property));
}
}
}
public class ValidationRules : INotifyPropertyChanged
{
string email = "";
public string EmailAddress
{
get
{
return email;
}
set
{
Console.WriteLine("Setting!");
//Only check if there is any text for now...
if (String.IsNullOrWhiteSpace(value))
{
throw new Exception();
}
email = value;
NotifyPropertyChanged("EmailAddress");
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(string property)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(property));
}
}
}
Xaml
<Window xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
x:Class="WpfApplication1.MainWindow"
Title="MainWindow" Height="125.078" Width="236.441" x:Name="UI" >
<Grid DataContext="{Binding ElementName=UI}">
<TextBox FontSize="15" HorizontalAlignment="Left" Name="txtEmail" VerticalAlignment="Top" Width="135"
Text="{Binding ValidationRules.EmailAddress, ValidatesOnExceptions=True, UpdateSourceTrigger=PropertyChanged}"/>
</Grid>
</Window>
I am new to WPF programming, so forgive me if this is a simple issue.
I have my Mainwindow.xaml set up like so:
<Window x:Class="GNMAwpf.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:GNMAwpf"
Title="Uploader"
Height="353" Width="342" ResizeMode="NoResize" WindowStartupLocation="CenterScreen">
<Window.DataContext>
<local:GinniNet x:Name="g" />
</Window.DataContext>
Further down I have a textblock:
<TextBlock Text="{Binding Path=StatusText}" Grid.ColumnSpan="2" MinWidth="150"></TextBlock>
In my class i have :
class GinniNet : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private string _statusMessage;
public string StatusMessage
{
get
{
return _statusMessage;
}
set
{
_statusMessage = value;
OnPropertyChanged("StatusMessage");
}
}
public GinniNet()
{
StatusMessage = "Ready";
}
private void OnPropertyChanged(string property)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(property));
}
}
When I change StatusMessage The textblock does not update ever. Where am i making my mistake?
Binding is incorrect -
<TextBlock Text="{Binding Path=StatusText}"/>
It should be -
<TextBlock Text="{Binding Path=StatusMessage}"
I want to do something like this diagram Data Binding Diagram.
If i update TextBox text Then update TextBlock text and Property and if i change Property Value then also update Textbox and textBlock text. Please tell me how can i do it using WPF ????
Thank`s For Help.
Im not sure if I understand right your question. Are the two Textboxes in the same view or in different?
Here a solution with 2 textboxes in the same view:
View (xaml):
<Window x:Class="Sandbox.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="mainWindow">
<StackPanel>
<TextBox Name="UpperTextBox" Text="{Binding ElementName=LowerTextBox, Path=Text,UpdateSourceTrigger=PropertyChanged}"/>
<TextBox Name="LowerTextBox" Text="{Binding MyValue, UpdateSourceTrigger=PropertyChanged}"/>
</StackPanel>
View-Codebehind (xaml.cs):
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
DataContext = new MyViewModel();
}
}
ViewModel:
public class MyViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
private string _myValue;
public string MyValue
{
get { return _myValue; }
set
{
_myValue = value;
OnPropertyChanged("MyValue");
}
}
}