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"/>
Related
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);
}));
};
}
}
Edit: I want to update the value of a textblock to the value of a random variable that is generated periodically on another class.
My implementation is blocking other features in the app (buttons). Any suggestion?
public partial class MainWindow : Window
{
TaskViewModel viewModel = new TaskViewModel();
public MainWindow()
{
this.DataContext = viewModel;
InitializeComponent();
Server_V2.AsyncService.runMain();
DisplayAV();
}
//Display Availability
private async void DisplayAV() {
while (true) {
//availabilityField.Text = Server_V2.AV.ToString();
viewModel.Availability = Server_V2.AV.ToString();
await Task.Delay(500);
}
}
public class TaskViewModel : INotifyPropertyChanged
{
private string availabilty = "0";
public string Availability
{
get { return availabilty; }
set { availabilty = value; OnStaticPropertyChanged();}
}
public event PropertyChangedEventHandler PropertyChanged;
public void OnStaticPropertyChanged([CallerMemberName] string PropertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(PropertyName));
}
}
You should use a background worker. You async code still runs on the main thread.
Like this...
BackgroundWorker worker = new BackgroundWorker();
public MainWindow()
{
this.DataContext = viewModel;
InitializeComponent();
Server_V2.AsyncService.runMain();
worker.DoWork += Worker_DoWork;
worker.RunWorkerAsync();
}
private void Worker_DoWork(object sender, DoWorkEventArgs e)
{
while (true)
{
//availabilityField.Text = Server_V2.AV.ToString();
viewModel.Availability = Server_V2.AV.ToString();
Thread.Sleep(500);
}
}
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();
}
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)
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.