I am building a WPF application that converts a powerpoint to WPF elements when you select one from a list.
I am using MVVM light to bind a ViewModel to my view and to add communication between ViewModels.
I have two views: OpenLocalView and PresentationView. When I select a powerpoint in the OpenLocalView, a message will be sent by MVVM light to the ViewModel of PresentationView and the MainViewModel with the path to that powerpoint. The MainViewModel switches the view to the PresentationView, and the PresentationViewModel executes this code to convert the powerpoint, and when that is finished, set the current slide so it is shown in the PresentationView:
public void StartPresentation(string location)
{
var scheduler = TaskScheduler.FromCurrentSynchronizationContext();
Loading = true;
Task.Factory.StartNew(() =>
{
var converterFactory = new ConverterFactory();
var converter = converterFactory.CreatePowerPointConverter();
_slides = converter.Convert(location).Slides;
},
CancellationToken.None,
TaskCreationOptions.LongRunning,
scheduler).ContinueWith(x =>
{
Loading = false;
CurrentSlide = _slides.First();
},
CancellationToken.None,
TaskContinuationOptions.OnlyOnRanToCompletion,
scheduler);
}
When the Loading property is set, the view gets updated with a "loading" message, to make the UI more responsive:
public Boolean Loading
{
get { return _loading; }
set
{
_loading = value;
RaisePropertyChanged("Loading");
}
}
The problem is, this executes properly the first time when I load a powerpoint: The view switches to the PresentationView, the "loading" message is displayed, and after the converting is finished, the message disappears and the slide is shown. But when I go back to the OpenLocalView, and choose another powerpoint, the OpenLocalView hangs and it switches to the PresentationView after the converter is finished, not showing the "loading" message at all.
For reference, I will add some more relevant code.
This is executed when a powerpoint is selected in the OpenLocalViewModel:
private void PerformOpenPresentation(string location)
{
Messenger.Default.Send<OpenPowerPointMessage>(new OpenPowerPointMessage {Location = location});
}
The MainViewModel is subscribed to the messenger and switches the view:
Messenger.Default.Register<OpenPowerPointMessage>(this,
delegate
{
if (_presentation == null) _presentation = new PresentationView();
CurrentView = _presentation;
});
The PresentationViewModel is subscribed to the messenger as well and executes the method shown above:
Messenger.Default.Register<OpenPowerPointMessage>(this, message => StartPresentation(message.Location));
So, what am I doing wrong? Again, it executes fine one time, then after that not anymore, although the same code is executed.
Maybe the UI isn't updated yet when you already start converting. Try waiting a few milliseconds between setting the Loading to true and the start of the converter thread :)
Look here:
var scheduler = TaskScheduler.FromCurrentSynchronizationContext();
Loading = true;
Task.Factory.StartNew(() =>
{
var converterFactory = new ConverterFactory();
var converter = converterFactory.CreatePowerPointConverter();
_slides = converter.Convert(location).Slides;
},
CancellationToken.None,
TaskCreationOptions.LongRunning,
here ----> scheduler).ContinueWith(x =>
{
Loading = false;
CurrentSlide = _slides.First();
},
CancellationToken.None,
TaskContinuationOptions.OnlyOnRanToCompletion,
scheduler);
You're starting the 'long running' task on the synchronization context, that is - on the UI thread.
Get rid of the scheduler in the long running task, leave it on continuation. :)
I suggest moving the "Loading = true" in to task start block.. but make sure to use dispatcher while setting "Loading" value. i may not give the actual reason for the problem but its worth a try...
something like this may help..
Task.Factory.StartNew(() =>
{
System.Windows.Application.Current.Dispatcher.BeginInvoke((Action)delegate()
{
Loading = true;
});
var converterFactory = new ConverterFactory();
var converter = converterFactory.CreatePowerPointConverter();
_slides = converter.Convert(location).Slides;
}
Ok, I did it with windows forms, but I think is the same
I create a Label in the Task thread and call the Invoke of the form
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
public void AddControl(Control control)
{
if (InvokeRequired)
{
this.Invoke(new Action<Control>(AddControl), new object[] { control });
return;
}
this.Controls.Add(control);
}
private void Form1_Load(object sender, EventArgs e)
{
Task.Factory.StartNew(() =>
{
var label = new Label
{
Location = new Point(0, 0),
Text = "hola",
ForeColor = Color.Black
};
this.Invoke(new Action<Control>(AddControl), new object[] { label });
});
}
}
EDIT
Ok how about using Dispather.Invoke I'm not sure if this is going to block the UI....
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
public void AddControl()
{
var l = new Label
{
Content = "Label",
Height = 28,
HorizontalAlignment = System.Windows.HorizontalAlignment.Left,
Margin = new Thickness(209, 118, 0, 0),
Name = "label1",
VerticalAlignment = System.Windows.VerticalAlignment.Top
};
Grid.Children.Add(l);
}
private void Grid_Loaded(object sender, RoutedEventArgs e)
{
Task.Factory.StartNew(() =>
{
Dispatcher.Invoke(new Action(AddControl), null);
});
}
}
Related
I made a C# application which iterates through an image folder in order to classify the images, The problem is that during the execution. After it classified some images, the program crashes without any error, it just freezes with no error message.
private void ImageProcessing(string filename)
{
panel1.Controls.Clear();
pictureBox1.Image = new Bitmap(filename);
var predictions = _logic.Classify(filename);
foreach (var item in predictions)
{
if (this.InvokeRequired)
{
this.BeginInvoke((MethodInvoker)delegate ()
{
new Label() { Text = $"{item.Label} : {item.Confidence}", Parent = panel1, Dock = DockStyle.Top };
});
}
else
{
new Label() { Text = $"{item.Label} : {item.Confidence}", Parent = panel1, Dock = DockStyle.Top };
}
}
this.CompareScores(predictions);
var biggestCompare = this.CompareScores(predictions);
switch (biggestCompare.Label)
{
case "Has no Lighter":
TurnON_NOK_Lamp();
break;
case "Has Lighter":
TurnOn_OK_Lamp();
break;
}
}
private void Start()
{
Thread.Sleep(1000);
if (filenames.Length == 0)
{
NoFileLabel.Visible = false;
this.TurnOFF_LightTower();
}
else
{
foreach (string filename in filenames)
{
ImageProcessing(filename);
Thread.Sleep(1000);
this.TurnOFF_LightTower();
pictureBox1.Image = null;
pictureBox1.Invalidate();
}
foreach (string filename in filenames)
{
File.Delete(filename);
}
}
}
Apparently you are calling Start() and hence ImageProcessing() methods from UI thread. If there are any long-running calculations (and I believe there are some) then UI will become unresponsive, because you are blocking message processing loop for a long time and all UI is based on this message processing.
I recommend using switching to async methods for any long-running calculations.
Check https://learn.microsoft.com/en-us/dotnet/standard/parallel-programming/task-based-asynchronous-programming for detailed information.
In your case the ImageProcessing() and Classify() methods could look like:
private async Task ImageProcessingAsync(string filename)
{
pictureBox1.Image = new Bitmap(filename);
var predictions = await _logic.ClassifyAsync(filename);
foreach (var item in predictions)
{
label1.Text = $"{item.Label} : {item.Confidence}";
}
//await CompareScoresAsync(predictions);
var biggestCompare = this.CompareScoresAsync(predictions);
switch (biggestCompare.Label)
{
case "Has no Lighter":
TurnON_NOK_Lamp();
break;
case "Has Lighter":
TurnOn_OK_Lamp();
break;
}
}
public Task<List<Prediction>> ClassifyAsync(string filename)
{
return Task.Run(() =>
{
// TODO: prepare the result
return result;
});
}
Also you don't need to create a new label every time, so you could put a label once in the designer and then only change its Text.
Thread.Sleep() also is not desirable since it's blocking the executing thread and UI again becomes unresponsive. You could use await Task.Delay() instead.
I have js function getState() that returns a css property(block or none) of an element but it isn't working, visual studio gives some Thread messages:
09-14 23:30:22.081 W/WebView ( 6707): java.lang.Throwable: A WebView method was called on thread 'Thread-12'. All WebView methods must be called on the same thread. (Expected Looper Looper (main, tid 2) {4aca651} called on null, FYI main Looper is Looper (main, tid 2) {4aca651})
09-14 23:30:22.081 W/WebView ( 6707): at android.webkit.WebView.checkThread(WebView.java:2539)
09-14 23:30:22.081 W/WebView ( 6707): at android.webkit.WebView.evaluateJavascript(WebView.java:1054)
Thread finished: #4
Can any one figure out where is the error? Also, when this be working, the returned type can be associated to a label text or it wouldn't be a simple string?
My code:
public static Label label1;
public static WebView webnav;
StackLayout parent = null;
public MainPage()
{
InitializeComponent();
webnav = new WebView
{
HeightRequest = 1000,
WidthRequest = 1000,
Source = "https://www.example.com/test.html",
};
webnav.Navigated += WebView_Navigated;
label1 = new Label
{
WidthRequest = 900,
HeightRequest = 60,
Text = "default text"
};
parent = new StackLayout();
parent.Children.Add(webnav);
parent.Children.Add(label1);
Content = parent;
}
public void WebView_Navigated(object sender, WebNavigatedEventArgs e)
{
Task.Run(JSRun);
}
public static string retorno;
public static async Task JSRun()
{
retorno = await webnav.EvaluateJavaScriptAsync("getState();");
}
All WebView methods must be called on the same thread...
FYI main Looper is Looper (main, tid 2)
You are using a Task.Run to execute a method that in turn calls EvaluateJavaScriptAsync and thus you are not on the Main/UI thread. That is what the error is trying to tell you.
So remove the Task.Run:
public void WebView_Navigated(object sender, WebNavigatedEventArgs e)
{
JSRun();
}
Or post your EvaluateJavaScriptAsync call to the UI message/looper queue:
Device.BeginInvokeOnMainThread(async() =>
{
retorno = await webnav.EvaluateJavaScriptAsync("getState();");
});
Trying to create a thread which will modify the window which is generated from the class. It will also start the ultra activity indicator. however when I run this code it will modify the window but will not add the ultra activity monitor and will just have a white rectangle where it would sit.
public void refreshNotification()
{
Thread backgroundThread = new Thread(
new ThreadStart(() =>
{
window.Size = new System.Drawing.Size(330, 100);
window.FormBorderStyle = System.Windows.Forms.FormBorderStyle.None;
window.TransparencyKey = System.Drawing.Color.Turquoise;
window.BackColor = System.Drawing.Color.Gainsboro;
window.MouseDown += window_MouseDown;
window.MouseMove += window_MouseMove;
window.MouseUp += window_MouseUp;
UltraActivityIndicator Indicator = customiseActivityIndicator();
window.Controls.Add(Indicator);
window.Show();
while (done == false)
{
Thread.Sleep(10);
//do nothing
}
Indicator.Stop();
}
));
backgroundThread.Start();
}
private static UltraActivityIndicator customiseActivityIndicator()
{
UltraActivityIndicator Indicator = new UltraActivityIndicator();
Indicator.Start();
Indicator.Location = new System.Drawing.Point(50, 35);
Indicator.Size = new System.Drawing.Size(230, 25);
Indicator.MarqueeAnimationStyle = Infragistics.Win.UltraActivityIndicator.MarqueeAnimationStyle.BounceBack;
Indicator.AnimationSpeed = (25);
Infragistics.Win.Appearance appearance12 = new Infragistics.Win.Appearance();
appearance12.BackColor = System.Drawing.Color.CornflowerBlue;
Indicator.MarqueeFillAppearance = appearance12;
return Indicator;
}
You are creating your window in separate Thread and after you put exactly that Thread into unlimit Sleep with this code:
while (done == false)
{
Thread.Sleep(10);
//do nothing
}
This is exactly why your window is white - it's busy from the Windows point of view.
I suggest you to create your window on UI Thread and use the BeginInvoke for resizing and indicator logic.
I recently encountered a situation for which I have no explanation so far
Trying to work on cross-threading operations with winform I wrote a short piece of code to test solutions faster.
I have a form including a progress bar, a a DataGridView and a Button
public MainForm()
{
this._progressBar = new ProgressBar { Dock = DockStyle.Top, };
this._button = new Button { Dock = DockStyle.Bottom, Text = #"&GO!" };
this._dataGridView = new DataGridView {Dock = DockStyle.Fill,};
this.Controls.Add(this.ProgressBar);
this.Controls.Add(this.Button);
this.Controls.Add(this.DataGridView);
this.WindowsFormsSynchronizationContext = WindowsFormsSynchronizationContext.Current as WindowsFormsSynchronizationContext;
this._records = new SpecialBindingList<Record>();
//this._records = new SpecialBindingList<Record>();
this.DataGridView.DataSource = this.Records;
this.Button.Click += this.button_Click;
}
The button has an event OnClick
private void button_Click(object sender, EventArgs e)
{
var dispatcherUI = Dispatcher.CurrentDispatcher;
Action action = () =>
{
while (true)
{
Task.Factory.StartNew(() =>
{
var value = (this.ProgressBar.Value == this.ProgressBar.Maximum)
? 0
: this.ProgressBar.Value + 1;
var record = new Record
{
A = DateTime.Now.Second.ToString(),
B = DateTime.Now.Millisecond.ToString()
};
if (Thread.CurrentThread != dispatcherUI.Thread)
{
dispatcherUI.BeginInvoke(new Action(() => { ProgressBar.Value = value; }), null);
}
this.WindowsFormsSynchronizationContext.Send((state) => this.Records.Add(record), null);
Thread.Sleep(100);
});
}
};
var task = new Task(action);
task.Start();
this.Button.Enabled = false;
}
It creates a task, which is filling the progress bar and adding new lines to the grid, then start it and disable the button.
The problem is, is I start the code like this the action (state) => this.Records.Add(record) would always throw an InvalidOperationException when adding a record to my Bindinglist.
However I realized that if I skip the this.Button.Enabled = false; line my code would execute without any problem !
This seems a little odd to me, so I was wondering why modifying a button property would creates an exception on an apparently unrelated operation
I have on winform with a textbox and on textchanged executes a background thread:
private void txtFathersLast_TextChanged(object sender, EventArgs e)
{
ThreadPool.QueueUserWorkItem(_ => WaitWhileUserTyping());
}
private void WaitWhileUserTyping()
{
var keepWaiting = true;
while (keepWaiting)
{
_keyPressed = false;
Thread.Sleep(TypingDelay);
keepWaiting = _keyPressed;
}
Invoke((MethodInvoker)(ExecuteSearch));
_waiting = false;
}
private void ExecuteSearch()
{
Thread.Sleep(200);
Task.Factory.StartNew(() =>
{
using (DataReference.SearchWCF search = new DataReference.SearchWCF())
{
_similaritySearchResults = search.SearchPersonBySimilarity(txtFathersLast.Text, txtMothersLast.Text, txtName.Text, DateTime.Now, 10);
}
}).ContinueWith(t=>{
if (this.InvokeRequired)
{
this.BeginInvoke(new Action(() =>
{
if (_similaritySearchResults != null && _similaritySearchResults.Tables["data"].Rows.Count > 0)
{
DataTable dt = _similaritySearchResults.Tables["data"];
Infragistics.Win.Misc.UltraTile newTile = null;
for (int index = 0; index < dt.Rows.Count; index++)
{
newTile = new Infragistics.Win.Misc.UltraTile("Person X");
newTile.Control = new CustomControls.Controls.PersonResult("123", "123", index + 150);
newTile.Tag = new Guid("90D27721-7315-4B86-9CFD-4F7D02921E9A");
newTile.DoubleClick += TileDoubleClick;
tilePanel.Tiles.Add(newTile);
}
}
}));
}
else
{
if (_similaritySearchResults != null && _similaritySearchResults.Tables["data"].Rows.Count > 0)
{
DataTable dt = _similaritySearchResults.Tables["data"];
Infragistics.Win.Misc.UltraTile newTile = null;
for (int index = 0; index < dt.Rows.Count; index++)
{
newTile = new Infragistics.Win.Misc.UltraTile("Person X");
newTile.Control = new CustomControls.Controls.PersonResult("123", "123", index + 150);
newTile.Tag = new Guid("90D27721-7315-4B86-9CFD-4F7D02921E9A");
newTile.DoubleClick += TileDoubleClick;
tilePanel.Tiles.Add(newTile);
}
}
}
}, TaskScheduler.FromCurrentSynchronizationContext());
}
This is working fine, the application goes to a database then get results and update the UI, adding tiles to a control depending of the number of records returned by database.
Now, the problem comes when I try to add another background thread into my custom control:
new CustomControls.Controls.PersonResult("123", "123", index + 150);
The code for the control is:
protected override void InitLayout()
{
// if I comment this then everything works fine
// but if I leave this, then the UI freezes!!
GetPictureAsync();
base.InitLayout();
}
/// <summary>
///
/// </summary>
private void GetPictureAsync()
{
// This line needs to happen on the UI thread...
TaskScheduler uiScheduler = TaskScheduler.FromCurrentSynchronizationContext();
Task.Factory.StartNew(() =>
{
Random sleep = new Random();
System.Threading.Thread.Sleep(sleep.Next(1000,3000));
if (this.pbPhoto.InvokeRequired)
{
this.pbPhoto.Invoke(new Action(() =>
{
this.Load(#"E:\Photos\" + PhotoId.ToString() + ".jpg");
//this.pbPhoto.Image = Utility.Common.GetResourceImage("woman_sample.jpg");
}));
}
else
{
this.Load(#"E:\Photos\" + PhotoId.ToString() + ".jpg");
//this.pbPhoto.Image = Utility.Common.GetResourceImage("woman_sample.jpg");
}
}, CancellationToken.None, TaskCreationOptions.None, uiScheduler);
}
So the problem seems to be that I first execute a thread for looking when to start search, then inside that thread I run another thread in order to get data from database, and then each control updated in the UI will run another thread to get a picture and update a picturebox.
Anyone knows how to solve this? or a way to work around this?
When you call
new CustomControls.Controls.PersonResult("123", "123", index + 150)
Is "123" a literal string, or are they being read from UI controls. For example,
new CustomControls.Controls.PersonResult(txtFathersName.Text", txtMothersName.Text, index + 150)
i cant test it right now, but isnt accessing the Text property not allowed from a thread other than the one that created the control?
I think the problem lies in you forcing the Task in GetPictureAsync to execute on UI thread and then you are calling Thread.Sleep(). This update UI in Task using TaskScheduler.FromCurrentSynchronizationContext question tackles the same problem as you are having. I would rewrite your code as:
private void async GetPictureAsync()
{
Random sleep = new Random();
await TaskEx.Delay(sleep.Next(1000,3000));
this.Load(#"E:\Photos\" + PhotoId.ToString() + ".jpg");
}