I'm new to WPF and I'm trying to make a simple app, a stopwatch. It works fine if I'm not doing the data binding. Here's my XAML.
<Window x:Class="StopWatch.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:s="clr-namespace:StopWatch"
Title="MainWindow" Height="318" Width="233">
<Window.Resources>
<s:StopWatchViewModel x:Key="swViewModel" x:Name="swViewModel"></s:StopWatchViewModel>
</Window.Resources>
<Grid DataContext="{StaticResource swViewModel}">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="auto"/>
<RowDefinition Height="128*" />
</Grid.RowDefinitions>
<TextBlock Grid.Row="1" Height="49" HorizontalAlignment="Left" Margin="42,50,0,0" Name="txtTime" Text="{Binding Path=Message}" VerticalAlignment="Top" Width="147" FontSize="20" TextAlignment="Center" />
<Button Content="Start" Grid.Row="2" Height="23" HorizontalAlignment="Left" Margin="12,15,0,0" Name="startBtn" VerticalAlignment="Top" Width="58" Click="startBtn_Click" />
<Button Content="Stop" Grid.Row="2" Height="23" HorizontalAlignment="Left" Margin="76,15,0,0" Name="stopBtn" VerticalAlignment="Top" Width="58" Click="stopBtn_Click" />
<Button Content="Reset" Grid.Row="2" Height="23" HorizontalAlignment="Left" Margin="140,15,0,0" Name="resetBtn" VerticalAlignment="Top" Width="59"/>
</Grid>
and here is the code in MainWindow
public partial class MainWindow : Window
{
private StopWatchViewModel stopwatch;
public MainWindow()
{
InitializeComponent();
stopwatch = new StopWatchViewModel();
}
private void startBtn_Click(object sender, RoutedEventArgs e)
{
stopwatch.Start();
}
private void stopBtn_Click(object sender, RoutedEventArgs e)
{
stopwatch.Stop();
}
}
and here's the code in StopWatchViewModel.cs
class StopWatchViewModel : INotifyPropertyChanged
{
private DispatcherTimer timer;
private Stopwatch stopwatch;
private string message;
public event PropertyChangedEventHandler PropertyChanged;
public string Message
{
get
{
return message;
}
set
{
if (message != value)
{
message = value;
OnPropertyChanged("Message");
}
}
}
public StopWatchViewModel()
{
timer = new DispatcherTimer();
stopwatch = new Stopwatch();
timer.Tick += new EventHandler(timer_Tick);
timer.Start();
stopwatch.Reset();
}
public void Start()
{
stopwatch.Start();
}
public void Stop()
{
stopwatch.Stop();
}
private void timer_Tick(object sender, EventArgs e)
{
Message = stopwatch.Elapsed.ToString(); // Doesn't work.
// Message = "hello"; does not work too!
}
private void OnPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
I don't know where I got it wrong.
EDIT: I got it working. So here's the working code for anyone's reference.
XAML, change the original to this
<Window x:Class="StopWatch.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:s="clr-namespace:StopWatch"
Title="MainWindow" Height="318" Width="233">
<Grid> // partial code
and in behind code, change the constructor based on Erno's suggestion.
public MainWindow()
{
InitializeComponent();
viewModel = new StopWatchViewModel();
this.DataContext = viewModel;
}
Thanks guys!
Your problem here is that you don't have any mechanism for letting WPF know that your property is updated. Basically you have two options here:
Make Message into a Dependancy Property.
Implement INotifyPropertyChanged so that it let's the GUI know when the message have been updated.
To make sure that you have all parts for getting INotifyPropertyChanged to work, check that you did all of this:
Define the event PropertyChanged.
Make a private NotifyPropertyChanged method to raise the event. This method should take a string parameter (name of the property) and raise the event like this : PropertyChanged(this, new PropertyChangedEventArgs(<nameofproperty>). The reason to make this method is to put the null check and invocation details in one place.
In the property setter, call NotifyPropertyChanged with the correct name (case-sensitive) of the property after the value has changed.
Just replace your Message property like this and it will work:
public string Message
{
get { return (string)GetValue(MessageProperty); }
set { SetValue(MessageProperty, value); }
}
public static readonly DependencyProperty MessageProperty =
DependencyProperty.Register("Message", typeof(string),
typeof(MainWindow), new UIPropertyMetadata(String.Empty));
EDIT after posting my solution I noticed you changed all the code into a ViewModel solution... feel free to ignore or go back to your first set of code.
In your new code you are creating TWO instances of the ViewModel, one in code and one in the resources. That is not good as you are manipulating the one in code and binding to the one in the resources(xaml)
EDIT:
change your constructor to this:
public MainWindow()
{
InitializeComponent();
stopwatch = new StopWatchViewModel();
this.DataContext = stopwatch;
}
That's all
Changes to the property won't get noticed if you don't implement INotifyPropertyChanged correctly. So you should do that first. Maybe you missed something when you did it earlier.
Related
I have a problem with INotifyPropertyChanged and binding, I watched dozen questions here, but problem still exists.
My XAML code:
<Window x:Class="DAA.IST.ChoosePC.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:DAA.IST.ChoosePC"
mc:Ignorable="d"
Title="MainWindow" Height="350" Width="525">
<Window.Resources>
<local:Question x:Key="ExpertSystem" Text="question"/>
</Window.Resources>
<Grid DataContext="{StaticResource ExpertSystem}">
<Label x:Name="Description" Content="Определение опимальной конфигурации ПК" HorizontalAlignment="Left" Margin="100,30,0,0" VerticalAlignment="Top"/>
<Button x:Name="button" Content="" HorizontalAlignment="Left" Margin="204,167,0,0" VerticalAlignment="Top" Width="75" Click="button_Click"/>
<Button x:Name="button1" Content="" HorizontalAlignment="Left" Margin="284,167,0,0" VerticalAlignment="Top" Width="75" Click="button1_Click"/>
<Label x:Name="QuestionLable" Content="{Binding Text}" HorizontalAlignment="Left" Margin="100,70,0,0" VerticalAlignment="Top"/>
<Label x:Name="Result" Content="" HorizontalAlignment="Left" Margin="100,210,0,0" VerticalAlignment="Top"/>
</Grid>
I list here full code, because I really don't know there problem is, sorry. Main window code:
public partial class MainWindow : Window
{
public static IQuestion Question;
public MainWindow()
{
InitializeComponent();
Question = new Question
{
Text = "question",
Answers = new List<IAnswer>
{
new Answer {Text = "answer", NextQuestionNumber = 0},
new Answer {Text = "answer1", NextQuestionNumber = 0}
}
};
DataContext = Question;
}
private void button_Click(object sender, RoutedEventArgs e)
{
Question.Answer(0);
}
private void button1_Click(object sender, RoutedEventArgs e)
{
Question.Answer(1);
}
}
And Questioncode, this class implements INotifyPropertyChanged:
public class Question : IQuestion, INotifyPropertyChanged
{
private string _text;
public string Text
{
get { return _text; }
set
{
_text = value;
OnPropertyChanged("Text");
}
}
public IList<IAnswer> Answers { get; set; }
public void Answer(int number)
{
MainWindow.Question.Answers[number].Number = number;
MainWindow.Question.Answers[number].SetNextQuestion();
}
public event PropertyChangedEventHandler PropertyChanged;
[NotifyPropertyChangedInvocator]
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
Please help me, sure I miss or don't know something. Also if someone can give me advise about changing of IList<IAnswer> Answers, I will be thankful to him^^
First of all, it is bad practice to refer MainWindow inside your question class.
You should use commands in this context i.e. you should define a command in your DataContext( question here) and bind that Command to your two Button's Command property.
For example:
Class Question : INotifyPropertyChanged, IQuestion
{
public ICommand ButtonClickCommand { get; set;}
public Question()
{
//Bind ButtonClickCommand to Event Handler/ A Method
ButtonClickCommand = new DelegateCommand(EventHandlerName);
}
public void EventHandlerName()
{
}
}
Also, take a private _answers field in the class and raise OnPropertyChanged event from Setter of Answers property too, to utilize this list in Notification mechanism i.e.
public IList<IAnswer> Answers {
get { return _answers }
set { _nswers = value;
OnPropertyChanged("Answers");
}
And, you have mentioned that you want to chnge the content of label and buttons. But to change the content of label and Buttons, you need to bind the 'Content' of these controls to Properties in Question class as well. I don't see any binding in your XAML as of now.
Finally, I found my mistake. I should change property, not create new instances. If I create new one, old value remains the same. It is connected with reference types realization some way.
MainWindow.Question.Text = "question";
MainWindow.Question.Answers[0].Text = "answer0";
MainWindow.Question.Answers[0].NextQuestionNumber = number0;
MainWindow.Question.Answers[1].Text = "answer1";
MainWindow.Question.Answers[1].NextQuestionNumber = number1;
The same questions has been asked many times on this site and I have read most of them. But I have a special problem (maybe?) that couldn't figure it out after hours of struggling and reading SO posts.
The problem is -simply explained, I have a WPF form which contains a Connect button. If this button is pressed a textblock must appear on that form, displaying the word "Connecting...". Upon pressing the button, some handshaking operations are done in the associated C# code which takes some time. If the program fails to connect, the textblock must change to "Failed!". Otherwise, it changes to "Succeed."
Now for this simple problem, I wrote in my XAML:
<Window x:Class="WpfTest.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="300" Width="200">
<Grid>
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition />
</Grid.RowDefinitions>
<Button x:Name="connecting" Content="Connect" FontWeight="Bold" Click="startConnection"
Width="60" HorizontalAlignment="Center" VerticalAlignment="Center" Grid.Row="0"/>
<TextBlock x:Name="comm_stat" Grid.Row="1" HorizontalAlignment="Center" VerticalAlignment="Center"
Text="{Binding Content}"/>
</Grid>
</Window>
And the C# code (inspired by this answer):
using System;
using System.Text;
using System.Windows;
using System.ComponentModel;
namespace WpfTest
{
public class DynamicObj : INotifyPropertyChanged
{
public DynamicObj() : this(string.Empty) { }
public DynamicObj(string txt) { Content = txt; }
private string _name;
public string Content
{
get { return _name; }
set {
_name = value;
OnPropertyChanged("Content");
}
}
public event PropertyChangedEventHandler PropertyChanged;
public void OnPropertyChanged(string PropertyName)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(PropertyName));
}
}
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
DataContext = this;
comm_stat.DataContext = new DynamicObj();
}
private void startConnection(object sender, RoutedEventArgs e)
{
comm_stat.Text = "Connecting...";
bool connect2device = false;
// do the handshaking operations. the result is then assigned to connect2device
comm_stat.Text = connect2device ? "Succeed." : "Failed!";
// some other operations
}
}
}
Now the problem is, whenever I click the button, no text is appeared in the textblock. Because the program waits for the startConnection method to reach its end and then updates the bonded textblock. But I want the textblock to change right after pressing the button. How can I do this?
You can use BackgroundWorker as such:
bool connect2device = false;
private void startConnection(object sender, RoutedEventArgs e)
{
comm_stat.Text = "Connecting...";
// do the handshaking operations. the result is then assigned to connect2device
BackgroundWorker worker = new BackgroundWorker();
worker.DoWork += DoWork;
worker.RunWorkerCompleted += Completed;
worker.RunWorkerAsync();
}
private void Completed(object sender, RunWorkerCompletedEventArgs e)
{
comm_stat.Text = connect2device ? "Succeed." : "Failed!";
}
private void DoWork(object sender, DoWorkEventArgs e)
{
//Change with actual work.
Thread.Sleep(1000);
connect2device = true;
}
One side note is that you actually do not use bindings to change the text. comm_stat.Text = "Connecting..."; sets the text property directly and the DynamicObj object is not used at all. It might be good for you to read a few tutorial on MVVM.
My class "ship" should change value of property "ShipOnePos" continuously. This value is binded to one of UI element (image) property. I like to update my UI according to "ShipOnePos" (when it is changed). So for this reason I use interface INotifyPropertyChanged, but UI is not updating, and PropertyChanged value is always null.
Can You advice what I miss, or what is wrong in this implementation ?
Class:
namespace DzienNaWyscigach
{
public class ship : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged([CallerMemberName] String propertyName = "")
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
private int MyShipOnePos;
public int ShipOnePos
{
get { return MyShipOnePos; }
set { MyShipOnePos = value;
NotifyPropertyChanged();
}
}
public void ShipsMovment()
{
DispatcherTimer timer = new DispatcherTimer();
timer.Interval = TimeSpan.FromMilliseconds(500);
timer.Tick += Timer_Tick;
timer.Start();
}
private void Timer_Tick(object sender, EventArgs e)
{
ShipOnePos= ShipOnePos+10;
}
}
}
UI Binding
<Image x:Name="ShipOne" HorizontalAlignment="Left" Height="71" Margin="31,32,0,0" VerticalAlignment="Top" Width="80" Source="Asets/ship1.png" RenderTransformOrigin="0.5,0.5">
<Image.DataContext>
<local:ship/>
</Image.DataContext>
<Image.RenderTransform>
<TransformGroup>
<ScaleTransform/>
<SkewTransform/>
<RotateTransform/>
<TranslateTransform X="{Binding Path=ShipOnePos, Mode=OneWay}"/>
</TransformGroup>
</Image.RenderTransform>
Code behind
private void BTN_Start_Click(object sender, RoutedEventArgs e)
{
ship Ships = new ship();
Ships.ShipsMovment();
}
The ship you are creating in your click isn't the ship you bound your Image to. You should probably create a ship as a resource and in your Click event retrieve that existing ship and start it moving. Or else, you might want to have a parent VM with a ship property.
So for example, you could do something like this:
<Window.Resources>
<local:ship x:Key="myShip"/>
</Window.Resources>
....
<Image x:Name="ShipOne"
HorizontalAlignment="Left" Height="71"
Margin="31,32,0,0" VerticalAlignment="Top" Width="80" Source="Asets/ship1.png"
RenderTransformOrigin="0.5,0.5"
DataContext="{StaticResource myShip}">
...
</Image>
And then in your click handler:
private void BTN_Start_Click(object sender, RoutedEventArgs e)
{
// Note: it might be worth caching this in a field or property
ship Ships = FindResource("myShip") as ship;
Ships.ShipsMovment();
}
Or alternatively, using the parent VM idea:
public class ParentVM : INotifyPropertyChanged
{
private ship _currentShip;
public ship CurrentShip
{
get { return _currentShip; }
set { _currentShip = value; NotifyPropertyChanged(); }
}
// ....
}
And then you could do:
<Window.Resources>
<local:ParentVM x:Key="myVM"/>
</Window.Resources>
...
<Grid DataContext="{StaticResource myVM}">
....
<Image DataContext="CurrentShip" ...>
....
</Image>
</Grid>
And:
// Note: this might be better handled with a command rather than a click handler
private void BTN_Start_Click(object sender, RoutedEventArgs e)
{
// Note: it might be worth caching this in a field or property
ParentVM vm = FindResource("myVM") as ParentVM;
vm.CurrentShip = new ship();
vm.CurrentShip.ShipsMovment();
}
I got an Problem with updating the text in a Textbox. I got this MainWindow:
<Window x:Class="TestDatabinding.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>
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition/>
<RowDefinition/>
</Grid.RowDefinitions>
<TextBox Grid.Row="0" Text="{Binding Text, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" Margin="10,10,10,10"/>
<Button Grid.Row="1" Content="Click me" Margin="10,10,10,10" Click="Button_Click"></Button>
<Button Grid.Row="2" x:Name="a1" Content="ShowText" Margin="10,10,10,10" Click="a1_Click" ></Button>
</Grid>
Now the cs-file for this MainWindow looks like:
using System.Windows;
namespace TestDatabinding
{
public partial class MainWindow : Window
{
MainWindowViewModel mwvm;
public MainWindow()
{
InitializeComponent();
mwvm = new MainWindowViewModel();
this.DataContext = mwvm;
}
private void Button_Click(object sender, RoutedEventArgs e)
{
mwvm.ChangeText();
this.DataContext = mwvm;
}
private void a1_Click(object sender, RoutedEventArgs e)
{
mwvm.showText();
}
}
}
And last but not least the ViewModel Class:
using System.ComponentModel;
using System.Windows;
namespace TestDatabinding
{
class MainWindowViewModel
{
public event PropertyChangedEventHandler PropertyChanged;
private string text;
public string Text
{
get { return this.text; }
set
{
this.text = value;
OnPropertyChanged("Text");
}
}
protected void OnPropertyChanged(string name)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(name));
}
}
public void ChangeText()
{
this.Text = "Hey paadddyy";
}
public void showText()
{
MessageBox.Show(Text);
}
}
}
I didn´t implement ICommands, because this is a simple test.
Now the Button's work correctly but the Textbox Text didn´t get updated.
Any suggestions what i can do? I only want to display "Hey paadddyy" when I click the first Button. After I press the second Button and then the first the MessageBox shows "Hey paadddyy" but the Textbox text didn´t get updated :(
Thank you for every hint ;)
Your MainWindowViewModel does not implement INotifyPropertyChanged. It needs to look like that:
class MainWindowViewModel: INotifyPropertyChanged
you define the event but does not implement the interface
It need to implement INotifyPropertyChanged
I suggested that if you want to do something with Notify Property. Another easy way is to apply Caliburn.Micro Framework to your project.
Follow this link.
My purpose is to add a textblock to my main UI window, of which text will be updated if needed. For that, in my UIWindow xaml I did like this:
<Window x:Class="UIDesigner.UIWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:s="clr-namespace:UIDesigner"
xmlns:c="clr-namespace:UIDesigner.Controls"
WindowStartupLocation="CenterScreen"
WindowState="Maximized"
WindowStyle="SingleBorderWindow"
Title="GUI"
Height="1000" Width="1400"
HorizontalAlignment="Center"
VerticalAlignment="Top"
Icon="Resources/Images/Logo.png"
>
<Grid Margin="0">
<Grid Grid.Row="1" Margin="0,10,0,0">
<GroupBox Header="Console" Grid.Column="1" Margin="0,590,0,0" HorizontalAlignment="Stretch" x:Name="consoleWindow" IsEnabled="True" VerticalAlignment="Stretch"
>
<TextBlock x:Name="myConsoleWindowTextBlock" Text="{Binding Path=consoleText}"/>
</GroupBox>
</Grid>
</Grid>
</Window>
This is the code behind:
using System.Windows;
using System.Runtime.CompilerServices;
using System.ComponentModel;
namespace UIDesigner
{
public partial class UIWindow : Window
{
public UIWindow()
{
InitializeComponent();
}
private string _consoleText;
public string consoleText
{
get{ return _consoleText;}
set
{
_consoleText = value;
NotifyPropertyChanged("consoleText");
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(string info)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(info));
}
}
}
}
Then in my main class, I call this UIWindow like this:
namespace UIDesigner
{
public partial class Main : Window
{
public Main()
{
InitializeComponent();
}
private void LoginButton_Click_1(object sender, RoutedEventArgs e)
{
var myUIWindow = new UIWindow();
myUIWindow.PropertyChanged += new PropertyChangedEventHandler(UIWindow_PropertyChanged);
myUIWindow.consoleText = "Hello User!";
myUIWindow.ShowDialog();
this.Close();
}
private void LoginButton_MouseEnter_1(object sender, MouseEventArgs e)
{
}
static void UIWindow_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
MessageBox.Show("Something Changed!");
MessageBox.Show(e.PropertyName);
}
}
}
Now I have two problems here:
First, when my UI window starts, I indeed received two message boxes, saying "something changed" followed by "consoleText". So that means the consoleText is changed successfully. But after my UIWindow shows up, the textblock is empty, I cannot see "Hello User!" there. Seems like Text="{Binding Path=consoleText} part is not working correctly in my xaml file.
Second and most importantly, I want to change the consoleText in another different class, namely in DesignerCanvas.Commands.cs. For that I couldn't figure out any solution. I want something like this in my DesignerCanvas.Commands.cs:
namespace UIDesigner
{
public partial class DesignerCanvas
{
private void changeConsoleOutput(string updatedConsoleText)
{
myUIWindow.consoleText = updatedConsoleText; //obviously, this is not working
}
}
}
Any kind of suggestion will be much appreciated.
1.First of two set the value in UI just add below one line
in constructor of UIWindow class
this.DataContext=this;
//because only specifying property consoletext, it will not able to know where to find consoletext.
2.u can find that UIwindow in App.Current.Windows and cast it to UIWindow type and then can
access the property.
foreach(Window win in App.Current.Windows)
{
if (win as UIWindow != null)
{
(win as UIWindow).consoletext = updatedConsoleText;
}
}
For second problem
Change
<TextBlock x:Name="myConsoleWindowTextBlock" Text="{Binding Path=consoleText}"/
To
<TextBlock x:Name="myConsoleWindowTextBlock" Text="{Binding Path=.}"/
and
in UIWindow constructor set
myConsoleWindowTextBlock.Datacontext=consoleText;