Event is fired but text doesn't change - c#

What im trying to do is to change Splash window label content.
App code behind is as follows
public partial class App : Application
{
private const int splashMinTime = 2000;
protected override void OnStartup(StartupEventArgs e)
{
Splash splashScr = new Splash();
splashScr.Show();
splashScr.SplashInfo = "Ładowanie ....";
Stopwatch splashTimer = new Stopwatch();
splashTimer.Start();
base.OnStartup(e);
MainWindow main = new MainWindow();
splashTimer.Stop();
int splashRemainingTime = splashMinTime - (int)splashTimer.ElapsedMilliseconds;
if (splashRemainingTime > 0)
Thread.Sleep(splashRemainingTime);
splashScr.Close();
}
}
Splash
public partial class Splash : Window, INotifyPropertyChanged
{
public string _SplashInfo;
public event PropertyChangedEventHandler PropertyChanged;
public Splash()
{
this.DataContext = this;
InitializeComponent();
}
public string SplashInfo
{
get { return _SplashInfo; }
set { _SplashInfo = value; OnPropertyChanged("SplashInfo"); }
}
private void OnPropertyChanged(string PropertyName)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(PropertyName));
}
}
}
And my Splash.xaml
<Grid>
<Image Source="Img\Splash.jpg" Stretch="None"/>
<Label x:Name="lblSplashInfo" Content="{Binding SplashInfo}" HorizontalAlignment="Left" Margin="10,204,0,0" VerticalAlignment="Top" Width="220"/>
</Grid>
Splash PropertyChangedEventHandler is fired but i don't see the changes in the splash window label.

It's a treading issue as Marcel_Bonzelet said. Try this and you will see:
protected override void OnStartup(StartupEventArgs e)
{
MainWindow window = new MainWindow();
window.Visibility = Visibility.Hidden;
new Task(() =>
{
Splash splashScr = null;
Dispatcher.Invoke(() =>
{
splashScr = new MainWindow();
splashScr.Show();
});
Stopwatch splashTimer = new Stopwatch();
splashTimer.Start();
splashScr.SplashInfo = "Ładowanie ....";
splashTimer.Stop();
int splashRemainingTime = splashMinTime - (int)splashTimer.ElapsedMilliseconds;
if (splashRemainingTime > 0)
Thread.Sleep(splashRemainingTime);
Dispatcher.Invoke(() =>
{
splashScr.Close();
window.Visibility = Visibility.Visible;
});
}).Start();
base.OnStartup(e);
}
Some explanation: the OnStartup method is called on the same thread where your label would be updated. So if you block this thread, and after that you immediately close the window, you won't be able to see the result of the binding.

Maybe I'm missing something, but for me it looks like you need some code like the following in your startup:
SplashScr.PropertyChanged += new PropertyChangedEventHandler<PropertyChangedEventArgs> (this.yourLabelChangerFunction);
And some function that changes the label:
public void yourLabelChangerFunction (object sender, EventArgs e){...}
Also, you seem to be setting string _SplashInfo before the window has been created, so maybe that's another reason why the window label doesn't change.

I think the binding is working but the window is not updated because you do Thread.Sleep in the Thread of the Splash window. You have to do it in its own Thread.

Related

Binding to the same property on multiple windows doesn't work

I ran into a quite confusing problem when developing a multi-window wpf application.
There are two windows, MainWindow and SecondWindow. The code of both is pretty simple:
MainWindow:
<Button Content="Change Property to 5" Click="ChangeProperty" HorizontalAlignment="Left" Margin="10,10,0,0" VerticalAlignment="Top" />
SecondWindow:
<Label Content="{Binding InstanceOfMyClass.value, NotifyOnSourceUpdated=True}"></Label>
The code behind the second Window is untouched, the code behind the first window is the following:
public partial class MainWindow : Window
{
SecondWindow w;
ViewModel vm;
public MainWindow()
{
InitializeComponent();
vm = new ViewModel() { InstanceOfMyClass = new MyClass() { value = 3 } };
w = new SecondWindow() { DataContext = vm };
w.Show();
}
private void ChangeProperty(object sender, RoutedEventArgs e)
{
vm.InstanceOfMyClass.value = 7;
}
}
And the view model class which implements INotifyPropertyChanged:
class ViewModel : INotifyPropertyChanged
{
private MyClass _instance;
public MyClass InstanceOfMyClass
{
get
{
return _instance;
}
set
{
_instance = value;
OnPropertyChanged("InstanceOfMyClass");
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string propertyName)
{
var handler = PropertyChanged;
if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
}
}
class MyClass
{
public int value { get; set; }
}
I expected the text block to change its text to 5 when I click the button.
The number "3" is correctly loaded on startup. The window also refreshes when I create a new instance of MyClass and set it as InstanceOfMyClass in my ViewModel.
But when I hit the button - or, even stranger, when I temporarily store InstanceOfMyClass, set it to null and reassign it with the saved variable - nothing happens.
Any idea why?
Thanks in advance!
Implement INotifyPropertyChanged in MyClass and try again. In ChangeProperty you change the value property, that doesn't notify the view about the change.
Or you can also try rewriting your ChangeProperty to the following:
vm.InstanceOfMyClass = new MyClass() { value = 7 };
Both of these approaches should fix the problem as far as I can see.

I need a way to assign and retrieve values between windows form (C#)

So, I'm doing a school project atm. The application needs to be able to calculate the area of squares, circles etc.
I have one form for each figure to calculate the area off. Right now I have a "main menu" and three forms (one for each figure) and I want to be able to assign a variable LatestResult within one form and access it from the main menu form.
Note: I want to be able to do this without "loading the variable into the new form" like this: Form1 FormMainMenu = new Form1(LatestResult)
I've been trying to work with Get & Set in my Variables.cs class, but I can't seem to make it work right, if it's possible to do it with this.
EDIT: Put my code in the post
My Variables.cslooks like this:
public static string latestresult2
{
get
{
return latestresult2;
}
set
{
latestresult2 = value;
}
}
And then I assign the value upon a button click in one form:
Variables.latestresult2 = breddeR + " * " + længdeR + " = " + resultat;
breddeR and længdeR are the int variables for my calculation and resultat is the result.
At last I try to do this in another form:
label1.Text = Variables.latestresult2;
EDIT 2:
From my MainView form
private void button1_Click(object sender, EventArgs e)
{
Form2 FormTrekant = new Form2();
FormTrekant.Show();
}
You may use the INotifyPropertyChanged interface to facilitate this. This interface works with both WinForms and WPF.
public class Form2 : Form, INotifyPropertyChanged
{
public string latestresult2;
public event PropertyChangedEventHandler PropertyChanged;
public string LatestResult2
{
get
{
return latestresult2;
}
set
{
latestresult2 = value;
this.OnPropertyChanged("LatestResult2");
}
}
private void OnPropertyChanged(string propertyName)
{
var handler = this.PropertyChanged;
if (handler == null)
{
return;
}
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
public class Form3 : Form, INotifyPropertyChanged
{
public string latestResult3;
public event PropertyChangedEventHandler PropertyChanged;
public string LatestResult3
{
get
{
return latestresult3;
}
set
{
latestresult3 = value;
this.OnPropertyChanged("LatestResult3");
}
}
private void OnPropertyChanged(string propertyName)
{
var handler = this.PropertyChanged;
if (handler == null)
{
return;
}
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
The INotifyPropertyChanged interface allows you to subscribe to another objects property changes. In the above code, the second form will raise the event when ever its LatestResult2 property has had its value changed.
Now you just have your Form1 subscribe to it.
public class MainForm : Form
{
private Form2 secondaryForm;
private Form3 thirdForm;
public string LatestValue {get; set;}
public void Form2ButtonClick() // Assume this happens in a button click event on MainWindow.
{
this.secondaryForm = new Form2();
this.secondaryForm.PropertyChanged += this.LatestValueChanged;
this.secondaryForm.Closing += this.ChildWindowClosing;
}
public void Form3ButtonClick() // Assume this happens in a button click event on MainWindow.
{
this.thirdForm = new Form3();
this.thirdForm.PropertyChanged += this.LatestValueChanged;
this.thirdForm.Closing += this.ChildWindowClosing;
}
private void LatestValueChanged(object sender, PropertyChangedEventArgs e)
{
// Do your update here.
if (sender == this.secondaryForm)
{
this.LatestValue = this.secondaryForm.LatestValue2;
}
else if (sender == this.thirdForm)
{
this.LatestValue = this.thirdForm.LatestValue3;
}
}
// Clean up our event handlers when either of the children forms close.
private void ChildWindowClosing(object sender, ClosingWindowEventHandlerArgs args)
{
if (sender == this.secondaryForm)
{
this.secondaryForm.Closing -= this.ChildWindowClosing;
this.secondaryForm.PropertyChanged -= this.LatestValueChanged;
}
else if (sender == this.thirdForm)
{
this.thirdForm.Closing -= this.ChildWindowClosing;
this.thirdForm.PropertyChanged -= this.LatestValueChanged;
}
}
}
Your MainWindow can now react to changes within Form2, without having to pass values around. One thing to note, is that you will want to unsubscribe from the event when the Form2 is closed. Otherwise you will leak.
You can specify the instance of the main view to the other view. This way, you can access the properties of the main view.
Some code to explaain;
public class MainView
{
public string LatestResult { get; set; }
}
public class ChildView
{
private readonly MainView MainView;
public ChildView(MainView mainView)
{
this.MainView = mainView;
}
public void Calculation()
{
//Some calculation
this.MainView.LatestResult = "Some result";
}
}
Now this code can be used like this:
var mainView = new MainView();
var childView = new ChildView(mainView);
childView.Calculation();
//mainView.LatestResult == "Some result"

Updating Bitmap from BackgroundWorker in MVVM/WPF

I'm trying to update a BitmapImage in the UI from a BackgroundWorker thread. I know enough about background workers to generally set them up, and how to use an ObservableCollection to update a list from a BackgroundWorker, but I'm struggling getting the image to update.
When I set
So far it looks like this:
XAML:
<Image Source="{Binding ImageSource}" />
ViewModel:
public class ViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public void NotifyPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
private BitmapImage ImageSource_;
public BitmapImage ImageSource
{
get { return ImageSource_; }
set { ImageSource_= value; NotifyPropertyChanged("ImageSource"); }
}
private BackgroundWorker UpdateImageBGW = new BackgroundWorker();
public ViewModel()
{
// this works fine
ImageSource = UpdateImage();
UpdateImageBGW.DoWork += new DoWorkEventHandler(UpdateImage_DoWork);
UpdateImageBGW.RunWorkerAsync();
}
private void UpdateImage_DoWork(object sender, DoWorkEventArgs e)
{
// this gets called fine and grabs the updated image, but setting it to
// ImageSource never updates the UI
ImageSource = UpdateImage();
}
}
The problem is you are trying to update a UI element from a background thread. You cannot interact with elements created on the UI thread from any other thread because of security reasons. If you want to update the UI from a background thread, do something like this:
Dispatcher.Invoke((Action)delegate() { /*update UI thread here*/ });
This method will create the bridge that allows you to talk to the UI thread. Check out this stackoverflow thread that has more example.
Best of Luck
use ObservableCollection like this:
public partial class MainWindow : Window
{
private ObservableCollection<int> myVar;
public ObservableCollection<int> MyProperty
{
get { return myVar; }
set { myVar = value; }
}
BackgroundWorker bw;
public MainWindow()
{
InitializeComponent();
this.DataContext = this;
MyProperty = new ObservableCollection<int>();
bw = new BackgroundWorker();
bw.DoWork += bw_DoWork;
bw.RunWorkerAsync();
}
void bw_DoWork(object sender, DoWorkEventArgs e)
{
for(int i = 0; i < 10;i++)
{
MyProperty.Add(i);
}
}
}
and xaml:
<ListBox HorizontalAlignment="Left" ItemsSource="{Binding MyProperty}" Height="224" Margin="93,50,0,0" VerticalAlignment="Top" Width="321"/>

PropertyChanged is null when calling thread from another class

I have MainWindow class on which im showing realtime chart that is specified in DataChart class. Now when I run my app, chart will start adding new data and refreshing, because I start new thread for this in constructor of DataChart class. But what I need is to start updating chart AFTER I click button defined in MainWindow class, not after app start. But when I start same Thred from MainWindow, chart does not update and PropertyChangedEventHandler is null.
In MainWindow:
private void connectBtn_Click(object sender, RoutedEventArgs e)
{
DataChart chart = new DataChart();
Thread thread = new Thread(chart.AddPoints);
thread.Start();
}
In DataChart:
public class DataChart : INotifyPropertyChanged
{
public DataChart()
{
DataPlot = new PlotModel();
DataPlot.Series.Add(new LineSeries
{
Title = "1",
Points = new List<IDataPoint>()
});
m_userInterfaceDispatcher = Dispatcher.CurrentDispatcher;
//WHEN I START THREAD HERE IT WORKS AND PROPERTYCHANGED IS NOT NULL
//var thread = new Thread(AddPoints);
//thread.Start();
}
public void AddPoints()
{
var addPoints = true;
while (addPoints)
{
try
{
m_userInterfaceDispatcher.Invoke(() =>
{
(DataPlot.Series[0] as LineSeries).Points.Add(new DataPoint(xvalue,yvalue));
if (PropertyChanged != null) //=NULL WHEN CALLING FROM MainWindow
{
DataPlot.InvalidatePlot(true);
}
});
}
catch (TaskCanceledException)
{
addPoints = false;
}
}
}
public PlotModel DataPlot
{
get;
set;
}
public event PropertyChangedEventHandler PropertyChanged;
private Dispatcher m_userInterfaceDispatcher;
}
I think the problem why chart is not updating is that PropertyChanged=null, but i cant figure out how to solve it. Im using OxyPlot if it helps.
MainWindow.xaml:
<oxy:Plot Model="{Binding DataPlot}" Margin="10,10,10,10" Grid.Row="1" Grid.Column="1"/>
Your problem is you are creating new instance of DataChart as local variable. How you you expect data binding would have subscribed its event?
DataBinding will have subscribed the event of instance which was set as the DataContext, so you need to call AddPoints on the same instance. Try the following:
private void connectBtn_Click(object sender, RoutedEventArgs e)
{
DataChart chart = (DataChart)this.DataContext;
Thread thread = new Thread(chart.AddPoints);
thread.Start();
}

looping through a folder of images in c# / WPF

So i'm trying to loop through a folder and change the image source each 2 seconds.
I think my code is right, but I seem to be missing something since my image won't update, but I don't get an error.
The code populates my array of files so it finds the pictures, I'm just doing something wrong to set the image source.
XAML code
<Grid>
<Image x:Name="Picture" Source="{Binding ImageSource}" Width="980" Height="760" HorizontalAlignment="Left" VerticalAlignment="Top" Margin="350,50,0,0"></Image>
<Grid>
C# code
private string[] files;
private System.Timers.Timer timer;
private int counter;
private int Imagecounter;
Uri _MainImageSource = null;
public Uri MainImageSource {
get
{
return _MainImageSource;
}
set
{
_MainImageSource = value;
}
}
public IntroScreen()
{
InitializeComponent();
this.Loaded += new RoutedEventHandler(this.MainWindow_Loaded);
}
private void MainWindow_Loaded(object sender, RoutedEventArgs e)
{
setupPics();
}
private void setupPics()
{
timer = new System.Timers.Timer();
timer.Elapsed += new ElapsedEventHandler(timer_Tick);
timer.Interval = (2000);
timer.Start();
files = Directory.GetFiles("../../Resources/Taken/", "*.jpg", SearchOption.TopDirectoryOnly);
Imagecounter = files.Length;
MessageBox.Show(Imagecounter.ToString());
counter = 0;
}
private void timer_Tick(object sender, EventArgs e)
{
counter++;
_MainImageSource = new Uri(files[counter - 1], UriKind.Relative);
if (counter == Imagecounter)
{
counter = 0;
}
}
Anyone know what I'm doing wrong ?
Updated code
XAML
<Image x:Name="Picture" Source="{Binding MainImageSource}" Width="980" Height="760" HorizontalAlignment="Left" VerticalAlignment="Top" Margin="350,50,0,0"></Image>
C#
public partial class IntroScreen : UserControl, INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string propertyName)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
handler(this, new PropertyChangedEventArgs(propertyName));
}
private string[] files;
private System.Timers.Timer timer;
private int counter;
private int Imagecounter;
Uri _MainImageSource = null;
public Uri MainImageSource
{
get
{
return _MainImageSource;
}
set
{
_MainImageSource = value;
OnPropertyChanged("MainImageSource");
}
}
public IntroScreen()
{
InitializeComponent();
this.Loaded += new RoutedEventHandler(this.MainWindow_Loaded);
}
private void MainWindow_Loaded(object sender, RoutedEventArgs e)
{
setupPics();
}
private void setupPics()
{
files = Directory.GetFiles("../../Resources/Taken/", "*.jpg", SearchOption.TopDirectoryOnly);
Imagecounter = files.Length;
counter = 0;
timer = new System.Timers.Timer();
timer.Elapsed += new ElapsedEventHandler(timer_Tick);
timer.Interval = (2000);
timer.Enabled = true;
timer.Start();
}
private void timer_Tick(object sender, EventArgs e)
{
counter++;
MainImageSource = new Uri(files[counter - 1], UriKind.Relative);
if (counter == Imagecounter)
{
counter = 0;
}
}
I'm not getting any error's but the image still isen't switching. I'm wondering if my paths are even working. Is there any way to test this ?
You have forgot to do notify the update to MainImageSource to the binding.
To do so, you have to implement the interface : INotifyPropertyChanged and define DataContext.
And, as written in the MSDN documentation "Setting Enabled to true is the same as calling Start, while setting Enabled to false is the same as calling Stop.".
Like this:
public partial class IntroScreen : Window, INotifyPropertyChanged
{
private string[] files;
private Timer timer;
private int counter;
private int Imagecounter;
BitmapImage _MainImageSource = null;
public BitmapImage MainImageSource // Using Uri in the binding was no possible because the Source property of an Image is of type ImageSource. (Yes it is possible to write directly the path in the XAML to define the source, but it is a feature of XAML (called a TypeConverter), not WPF)
{
get
{
return _MainImageSource;
}
set
{
_MainImageSource = value;
OnPropertyChanged("MainImageSource"); // Don't forget this line to notify WPF the value has changed.
}
}
public IntroScreen()
{
InitializeComponent();
DataContext = this; // The DataContext allow WPF to know the initial object the binding is applied on. Here, in the Binding, you have written "Path=MainImageSource", OK, the "MainImageSource" of which object? Of the object defined by the DataContext.
Loaded += MainWindow_Loaded;
}
private void MainWindow_Loaded(object sender, RoutedEventArgs e)
{
setupPics();
}
private void setupPics()
{
timer = new Timer();
timer.Elapsed += timer_Tick;
timer.Interval = 2000;
// Initialize "files", "Imagecounter", "counter" before starting the timer because the timer is not working in the same thread and it accesses these fields.
files = Directory.GetFiles(#"../../Resources/Taken/", "*.jpg", SearchOption.TopDirectoryOnly);
Imagecounter = files.Length;
MessageBox.Show(Imagecounter.ToString());
counter = 0;
timer.Start(); // timer.Start() and timer.Enabled are equivalent, only one is necessary
}
private void timer_Tick(object sender, EventArgs e)
{
// WPF requires all the function that modify (or even read sometimes) the visual interface to be called in a WPF dedicated thread.
// IntroScreen() and MainWindow_Loaded(...) are executed by this thread
// But, as I have said before, the Tick event of the Timer is called in another thread (a thread from the thread pool), then you can't directly modify the MainImageSource in this thread
// Why? Because a modification of its value calls OnPropertyChanged that raise the event PropertyChanged that will try to update the Binding (that is directly linked with WPF)
Dispatcher.Invoke(new Action(() => // Call a special portion of your code from the WPF thread (called dispatcher)
{
// Now that I have changed the type of MainImageSource, we have to load the bitmap ourselves.
BitmapImage bitmapImage = new BitmapImage();
bitmapImage.BeginInit();
bitmapImage.UriSource = new Uri(files[counter], UriKind.Relative);
bitmapImage.CacheOption = BitmapCacheOption.OnLoad; // Don't know why. Found here (http://stackoverflow.com/questions/569561/dynamic-loading-of-images-in-wpf)
bitmapImage.EndInit();
MainImageSource = bitmapImage; // Set the property (because if you set the field "_MainImageSource", there will be no call to OnPropertyChanged("MainImageSource"), then, no update of the binding.
}));
if (++counter == Imagecounter)
counter = 0;
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string propertyName)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
}
And your XAML does not refer to the correct property:
<Grid>
<Image x:Name="Picture" Source="{Binding MainImageSource}" Width="980" Height="760" HorizontalAlignment="Left" VerticalAlignment="Top" Margin="350,50,0,0"></Image>
<Grid>
Why do you need to implement INotifyPropertyChanged?
Basically, when you define a binding, WPF will check if the class that contains the corresponding property defines INotifyPropertyChanged. If so, it will subscribe to the event PropertyChanged of the class.
I'm not seeing any use of the INotifyPropertyChanged interface, which would be required to update a UI item the way you are using it. As it is now, the UI control has no way of knowing that the value was updated.

Categories