PropertyChanged is null when calling thread from another class - c#

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();
}

Related

How to prevent collapsing of Expander listitem when refresh List with new DataContext in WPF

I made a List of Expanders in WPF. The List is bounded to an array of objects, and I need to update them periodically.
When I update an array of objects by reading them from DB after I expanded one of Expanders in List, the Expander automatically collapse.
Is there a way to prevent that?
Edit 1
This is the part of xml of ObjectTab,
<ListView x:Name="list" HorizontalContentAlignment="Stretch" VerticalContentAlignment="Stretch" ItemsSource="{Binding Path=Objects}" ScrollViewer.CanContentScroll="False">
<ListView.ItemTemplate>
<DataTemplate>
<local:ObjectRealtimeControl></local:ObjectRealtimeControl>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
and the code of ObjectTab is
public partial class ObjectTab : UserControl, INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public SomeObject[] Objects { get; set; }
public ObjectTab ()
{
DataContext = this;
InitializeComponent();
runWorker();
}
// Create the OnPropertyChanged method to raise the event
protected void OnPropertyChanged(string name)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
}
private void runWorker()
{
worker = new BackgroundWorker();
worker.WorkerSupportsCancellation = true;
worker.DoWork += Worker_DoWork;
worker.RunWorkerAsync();
}
private void Worker_DoWork(object sender, DoWorkEventArgs e)
{
BackgroundWorker worker = sender as BackgroundWorker;
while (!worker.CancellationPending )
{
refreshAll();
Thread.Sleep(1000);
}
}
private void refreshAll()
{
Shared.DB.read("Some SQL", (ex, dataTable) =>
{
Objects = dataTable.AsEnumerable().Select((row)=>{
return new SomeObject() {
id = row[0].toString(),
};
}).ToArray();
OnPropertyChanged("Objects");
}
}
}
ObjectRealtimeControl contains Expander, other controls to represent SomeObject class.
You haven't provided a Minimal, Complete, and Verifiable example of your issue so it's hard to say anything about your expanders but instead of raising the PropertyChanged event for the Objects property itself, you could try to update the individual properties of each object or clear the same collection and re-populare it. Something like this:
public partial class ObjectTab : UserControl
{
public ObservableCollection<SomeObject> Objects { get; set; } = new ObservableCollection<SomeObject>();
public ObjectTab()
{
DataContext = this;
InitializeComponent();
runWorker();
}
...
private void refreshAll()
{
Shared.DB.read("Some SQL", (ex, dataTable) =>
{
var newObjects = dataTable.AsEnumerable().Select((row) => {
return new SomeObject()
{
id = row[0].toString(),
};
}).ToArray();
Dispatcher.BeginInvoke(new Action(() =>
{
Objects.Clear();
foreach (var newObject in newObjects)
Objects.Add(newObject);
}));
};
}
}

Updating viewmodel from worker thread throws cross thread issue

Recently, I've been testing with binding of type which implements INotifyPropertyChanged and updating property from worker thread throwing cross thread issue.
Here is sample code :
public class MyViewModel : INotifyPropertyChanged
{
private string _name;
public string Name
{
get { return _name; }
set
{
if (_name != value)
{
_name = value;
OnPropertyChanged("Name");
}
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string propertyName)
{
PropertyChangedEventHandler hanlder = PropertyChanged;
if(hanlder != null)
hanlder(this, new PropertyChangedEventArgs(propertyName));
}
}
above viewmodel has been bind with label text in windows form and updating label value from worker thread.
Updating label1 text from worker thread causes cross thread issue :
public partial class MainForm : Form
{
private MyViewModel _myViewModel = new MyViewModel();
public MainForm()
{
InitializeComponent();
Btn1.Click += Btn1_Click;
label1.DataBindings.Add("Text", _myViewModel, "Name");
}
private void Btn1_Click(object sender, EventArgs e)
{
Task.Factory.StartNew(() =>
{
_myViewModel.Name = "Updating from worker thread!"; It causes cross thread issue
});
}
}
So far, I can believe is that it is due to updating UI from worker thread. Is there any work around to make it thread safe without changing in button click method i.e. probably making thread safe in viewmodel.
Grab the UI's SynchronizationContext (using SynchronizationContext.Current from the UI thread when the app starts, for example), and store it in some static variable somewhere (i've called it uiSynchronizationContext).
Then on your OnPropertyChanged do something like:
protected virtual void OnPropertyChanged(string propertyName)
{
uiSynchronizationContext.Post(
o => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName))
,null
);
}
Or you can use Send instead of Post if you want that operation synchronous (synchronous to the thread that started the send/post operation, it'll always be "synchronous" on the UI thread)
I particulary don't like doing direct databinding when multithreading (I prefer to poll with a timer on the UI thread from some graph object with the changes), but this should solve the problem.
I admiteddly haven't tested it
Did you try with CheckForIllegalCrossThreadCalls ?
Like this :
private void Btn1_Click(object sender, EventArgs e)
{
Task.Factory.StartNew(() =>
{
CheckForIllegalCrossThreadCalls = false;
_myViewModel.Name = "Updating from worker thread!"; It causes cross thread issue
CheckForIllegalCrossThreadCalls = true;
});
}
I don't say that is the best way but it's working for me when I work with thread
EDIT :
private void Btn1_Click(object sender, EventArgs e)
{
Task.Factory.StartNew(() =>
{
_myViewModel.Invoke((MethodInvoker)(() => _myViewModel.Name = "Updating from worker thread!"; ));
});
}

Event is fired but text doesn't change

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.

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"/>

WPF DataBinding of a label

im now trying for hours to get the databinding run.
But whatever im trying nothing works.
After a few thousend examples and retries (feels like a ffew thousand) i decided to make a new thread for my problem.
I have a window where you can select a woker.
On these window there are a UserControl which shows the details of the selected worker.
It would be nice to have all labels / textboxes / comboboxes filled automatically in case the selected worker changed.
For that the UserControl has a Property "ShownWorker" which contains the selected worker.
Worker Class:
public class Worker : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
private string id;
public string ID
{
get
{
return id;
}
set
{
id = value;
OnPropertyChanged("ID");
}
}
public Worker()
{
}
}
UserControl:
private Worker shownWorker;
public Worker ShownWorker
{
get
{
return shownWorker;
}
set
{
shownWorker = value;
}
}
public WorkerDetails()
{
InitializeComponent();
this.DataContext = shownWorker;
}
Label on the UserControl:
<Label Height="28" Margin="129,6,6,0" Name="labelWorkerID" VerticalAlignment="Top" Content="{Binding ID, Mode=OneWay, UpdateSourceTrigger=PropertyChanged, NotifyOnSourceUpdated=True}"></Label>
And im setting the ShownWorker like that:
private void dataGridAvaibleWorker_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
if (dataGridAvaibleWorker.SelectedItem is Worker)
{
var selectedWorker= (Worker)dataGridAvaibleWorker.SelectedItem;
WorkerDetails.ShownWorker = selectedWorker;
}
}
But nothing happens. Whats wrong?
I dont get it.
Aside from the property change notification, you have another issue:
These two properties:
ShownWorker
DataContext
Are pointing to the same reference when the form opens - e.g.
Worker someWorker = new Worker();
ShownWorker = someWorker;
DataContext = ShownWorker;
Changing ShownWorker here does not affect DataContext
ShownWorker = new Worker();
DataContext at this point still references the original worker - when you do this assignment DataContext = ShownWorker, DataContext references the worker you instantiated in the first of the three lines, it does not refer to the instance that ShownWorker is pointing to
To try and explain it a bit better:
// I'll stick some 'tags' on to shown how the instances will be referenced
Worker someWorker = new Worker(); // someWorker = "Instance1"
ShownWorker = someWorker; // ShownWorker points to "Instance1"
DataContext = ShownWorker; // DataContext points to "Instance1"
ShownWorker = new Worker(); // ShownWorker now points to "Instance2"
// DataContext still points to "Instance1"
You need to set DataContext not ShownWorker and raise a property changed event for DataContext
A better way to do this is to use an MVVM approach e.g.
public class WorkerViewModel : INotifyPropertyChanged
{
// Put standard INPC implementation here
public event PropertyChanged.... etc
private Worker shownWorker;
public Worker ShownWorker
{
get
{
return shownWorker;
}
set
{
if(value == shownWorker) return; // Don't do anything if the value didn't change
shownWorker = value;
OnPropertyChanged("ShownWorker");
}
}
public WorkerViewModel()
{
ShownWorker = // Get/create the worker etc
}
}
Now you have a VM you can set the DataContext to:
public WorkerViewModel WorkerViewModel { get; private set; }
public WorkerDetails()
{
InitializeComponent();
WorkerViewModel = new WorkerViewModel();
this.DataContext = WorkerViewModel;
}
And updates can be done to the VM instead:
WorkerViewModel.ShownWorker = someWorker;
Make sure you set your bindings in the XAML
<UserControl>
<SomeControl DataContext="{Binding ShownWorker}" />
</UserControl>
Instead of rolling your own MVVM stuff - I'd suggest looking at some popular MVVM frameworks as they can make working with WPF/Silverlight a breeze.
My personal fave is Caliburn Micro but there are plenty out there (MVVMLight, Prism, etc)

Categories