Equivalent to InvokeRequired in WPF - c#

Is there an equivalent to Form.InvokeRequired in WPF, e.g. Dispatcher.InvokeRequired?

This is slightly odd as it doesn't appear in intellisense, but you can use:
var dispatcher = myDispatcherObject.Dispatcher;
if (dispatcher.CheckAccess()) { /* ... */ }
As all UI components inherit from DispatcherObject this should solve your specific problem, but it is not specific to the UI thread - it can be used for any dispatcher.

The equivalent is Dispatcher.CheckAccess.

A possible solution that came to mind is:
if ( Dispatcher.Thread.Equals( Thread.CurrentThread ) )
{
Action( );
}
else
{
Dispatcher.Invoke( Action );
}

If you're building a Windows Store app, the above example won't work. Here's an example that does work. Modify as needed, of course :)
/// <summary>
/// Updates the UI after the albums have been retrieved. This prevents the annoying delay when receiving the albums list.
/// </summary>
/// <param name="albums"></param>
public void UpdateUiAfterAlbumsRetrieved(System.Collections.ObjectModel.ObservableCollection<PhotoAlbum> albums)
{
if (!Dispatcher.HasThreadAccess)
{
Dispatcher.RunAsync(CoreDispatcherPriority.Normal, () =>
{
ddlAlbums.DataContext = albums;
ddlAlbums.IsEnabled = true;
tbxProgress.Text = String.Empty;
ProgressBar.IsIndeterminate = false;
ProgressBar.Visibility = Windows.UI.Xaml.Visibility.Collapsed;
});
}
else
{
ddlAlbums.DataContext = albums;
ddlAlbums.IsEnabled = true;
tbxProgress.Text = String.Empty;
ProgressBar.IsIndeterminate = false;
}
}

Related

c# Multi threading operation triggered by a form

Good day,
After being unsuccessful in my Google & Stackoverflow queries (not sure what I should search for), I'm posting here a public question.
I have a main form (frmMainMenu). Whenever this form is loaded and a button on this form is pressed, I'm trying to update a chart. However, as this could be CPU-demanding, I was considering calling the operation to retrieve the chart from another thread.
I'm constantly getting an error : Cross-thread operation not valid: Control 'labelX2' accessed from a thread other than the thread it was created on. System.Windows.Forms at System.Windows.Forms.Control.get_Handle()
The idea behind is also to display this symbol (referenced as the circularProgress1 control whenever data is being retrieved and to hide it whenever the operation is complete.
Here below is the code of my method in the userform :
[...]
private void feedDashboard()
{
Thread l_Thread;
ClsStartPgm l_Start = null;
try
{
this.Invoke(new MethodInvoker(delegate()
{
l_Thread = new Thread(() =>
{
try
{
l_Start = new ClsStartPgm();
l_Start.getDashboardInformations(
this.labelX2,
this.circularProgress1,
this.tmr_Progress,
this.chart1,
this.expandablePanel_1);
}
finally
{
// onCompleted();
}
});
l_Thread.Start();
}));
}
catch (Exception exc)
{
ClsFinappErrorManager.manageException(exc);
}
finally
{
}
}
[...]
and if it can be helpful, here is my mentioned getDashboardInformations method of my Class ClsStartPgm
public sealed class ClsStartPgm
{
[...]
// (Constructors, accessors and other methods not mentioned here
// to simplify the reading of my question)
[...]
public void getDashboardInformations(
LabelX pInformationText,
Controls.CircularProgress pCircularProgress,
System.Windows.Forms.Timer pTimer,
Chart pChart1,
ExpandablePanel pExpandablePanel1)
{
List<double> l_DoubleList = null;
List<string> l_StringList = null;
try
{
pTimer.Start();
this.m_Busy_Dashboard_Generation = true;
this.m_Busy_Dashboard_Generation = false;
double[] yValues = l_DoubleList.ToArray();
string[] xValues = l_StringList.ToArray();
pChart1.Series["MySerie"].Points.Clear();
// Populate series data
pChart1.Series["MySerie"].Points.DataBindXY(xValues, yValues);
// Set Doughnut chart type
pChart1.Series["MySerie"].ChartType = SeriesChartType.Pie;
// Set title of the expendable panel
pExpandablePanel1.TitleText = "Situation: " + DateTime.Now.ToShortDateString();
[...]
}
catch (Exception exc)
{
ClsFinappErrorManager.manageException(exc);
}
finally
{
l_Tuple = null;
l_Accounts = null;
}
}
}
Please, could anyone guide me on what's wrong in my code? I'm definitely not asking to get the code written for me. However, I would be keen on understanding what I'm doing incorrectly here in my approach.
Many thanks for your appreciated help and best wishes,

How to show a WPF window from Class Library (dll) project?

I have recently made a Class Library (dll) for my other project to program a Bluetooth device via serial port (COM). The library is used to transfer firmware via COM port. It works fine until the requirement comes, which requires a WPF window to show the progress of programming. I have successfully created the progress bar using standard WPF app template. However, the standard WPF does not allow me to generate dll. After searching here, I found this link that teaches you how to add a WPF window to existing Class Library project. Also, someone teaches you how to show the window from here. Everything look good until I tried, there is nothing shows up when I call the method ProgrammBluetooth() from LabVIEW.
My main method, which is in a separate .cs file:
namespace BTMProg
{
public class BTMProgrammer
{
private bool _uut1Status = false;
private string _uut1Message = "";
public bool UUT1Status
{
get { return _uut1Status; }
set { _uut1Status = value; }
}
public string UUT1Message
{
get { return _uut1Message; }
set { _uut1Message = value; }
}
public void ProgramBluetooth (string ioPort, string firmwareFile)
{
List<UUT> uutList = new List<UUT>();
uutList.Add(new UUT(ioPort, "UUT1", 1));
Thread thread = new Thread(() =>
{
var wn = new MainWindow(uutList, firmwareFile);
wn.ShowDialog();
wn.Closed += (s, e) => wn.Dispatcher.InvokeShutdown();
Dispatcher.Run();
if (wn.TaskList[0].Result.ToUpper().Contains("SUCCESS"))
{
_uut1Status = true;
_uut1Message = wn.TaskList[0].Result;
}
else
{
_uut1Status = false;
_uut1Message = wn.TaskList[0].Result;
}
});
thread.SetApartmentState(ApartmentState.STA);
thread.Start();
}
}
}
My WPF code in MainWindow.xaml.cs:
ProgrammingViewModel _pvm = new ProgrammingViewModel();
private List<string> _viewModeList = new List<string>();
private List<Task<string>> _taskList = new List<Task<string>>();
public List<Task<string>> TaskList {
get => _taskList;
set => _taskList = value;
}
public MainWindow(List<UUT> uutList, string firmwareFile)
{
InitializeComponent();
foreach (var uut in uutList)
{
_viewModeList.Add(uut.UutName);
}
_pvm.AddProcessViewModels(_viewModeList);
ProgressBarView.DataContext = _pvm.ProcessModels;
StartProgramming(uutList, firmwareFile);
Application.Current.MainWindow.Close();
}
The issue before was that if I don't use dispatcher to create a new thread, an exception saying "The calling thread must be STA, because many UI components require this...." thrown. After I use the new thread, no error but the window does not show up as expected. What could be the problem? Thanks.
The ShowDialog function will stop execution of the thread until the window closes, meaning the rest of that code may not run and the dispatcher may not be started. You should try the Show method instead, which returns as soon as the window is shown.
Also, what is going on with these lines in the constructor of the window?
StartProgramming(uutList, firmwareFile);
Application.Current.MainWindow.Close();
Whatever that first line does, it needs to return and not do a bunch of work if you want the window to finish getting constructed. The second line makes no sense at all. Why are you closing the main window of the application? Did you even set and open a window associated with that property at some point?
I suspect one or more of these things is preventing the thread from ever reaching the point where it can show the window.

wpf dispatcher error in tag property

I have an error accessing a WPF element in code behind (see indicated error line):
Thumb sThumb = new Thumb();
...
TextBox Thumbtxt = (TextBox)sThumb.Template.FindName("TextThumb", sThumb);
if (Thumbtxt.Tag != null) <-- Unhandled Exception: The calling thread cannot access this object because a different thread owns it.
{
if (Thumbtxt.Tag.ToString() == "Disabled")
IsDisabled = true;
}
I then tried to execute the code in the dispatcher, as follows:
System.Windows.Application.Current.Dispatcher.Invoke(DispatcherPriority.Normal, new Action(() =>
{
Thumb sThumb = new Thumb();
...
TextBox Thumbtxt = (TextBox)sThumb.Template.FindName("TextThumb", sThumb);
if (Thumbtxt.Tag != null)
{
if (Thumbtxt.Tag.ToString() == "Disabled")
IsDisabled = true;
}));
Still, I get the same error in the very same line. I use this dispatcher invoke in other parts and it works fine, but apparently it doesn't like the "Tag" property, for some reason.
Can someone please shed some light?
Thanks in advance
Try to use TaskScheduler.FromCurrentSynchronizationContext
await Task.Run(() => { TextBox Thumbtxt = (TextBox)sThumb.Template.FindName("TextThumb", sThumb); })
.ContinueWith(
res =>
{
you code here
},
TaskScheduler.FromCurrentSynchronizationContext());
Try to use the dispatcher that is associated with the Thumbtxt control:
Thumbtxt.Dispatcher.Invoke(new Action(()=>
{
if (Thumbtxt.Tag != null)
{
if (Thumbtxt.Tag.ToString() == "Disabled")
IsDisabled = true;
};
}));

Mixed use of Task and Dispatcher halts the task

Explanation
I'm creating my own search control in WPF. This control is a UserControl that contains an area with search parameters (eg.: search on specific ID, name,...) and a GridView that shows the result.
In my control I have a dependency property of type ICommand where I bind the command to execute my search query.
public static readonly DependencyProperty SearchCommandProperty =
DependencyProperty.Register("SearchCommand", typeof(ICommand), typeof(SearchControl));
Use of my control in a certain window:
<customControls:SearchControl SearchCommand="{Binding SearchItemsCommand}"
SearchResult="{Binding SearchResult}" />
SearchItemsCommand is a Command in my ViewModel where I can find my search query.
In this command you can find my query to retrieve the result.
SearchResult is my ICollection that contains the result of the search query.
Code of the commands
Viewmodel
private DelegateCommand searchItemsCommand;
public DelegateCommand SearchItemsCommand
{
get
{
if (this.searchItemsCommand== null)
this.searchItemsCommand= new DelegateCommand(this.SearchItemsCommandExecuted);
return this.searchItemsCommand;
}
}
private ICollection<VoucherOverviewModel> voucherResults;
private void SearchItemsCommandExecuted()
{
using (DbContext context = new DbContext())
{
var query = (from v in context.Vouchers
join vt in context.VoucherTransactions on new
{
voucherID = v.VoucherID,
type = VoucherTransactionType.Out
} equals new
{
voucherID = vt.VoucherID,
type = vt.Type
}
join vtype in context.VoucherTypes on v.VoucherTypeID equals vtype.VoucherTypeID
join c in context.Customers on vt.CustomerID equals c.CustomerID
join pos in context.PointOfSales on v.PointOfSaleID equals pos.PointOfSaleID
select new VoucherOverviewModel()
{
PointOfSaleID = v.PointOfSaleID,
PointOfSaleName = pos.Name,
VoucherID = v.VoucherID,
VoucherCode = v.Code,
VoucherTypeID = v.VoucherTypeID,
VoucherTypeDescription = vtype.Code,
CustomerID = c.CustomerID,
CustomerName = c.Name,
Value = vt.Value,
UsedValue = context.VoucherTransactions
.Where(x => x.VoucherID == v.VoucherID &&
x.Type == VoucherTransactionType.In)
.Sum(x => x.Value),
CreateDate = vt.Date,
ValidFrom = v.ValidFrom,
ValidUntil = v.ValidUntil,
ParentVoucherID = v.ParentVoucherID,
Comment = v.Comment,
});
foreach (ISearchParameter searchParameter in this.SearchParameters)
{
if (!searchParameter.Value.IsNullOrDefault())
{
switch ((FilterVoucherParameterKey)searchParameter.Key)
{
case FilterVoucherParameterKey.CustomerID:
query = query.Where(x => x.CustomerID == (int)searchParameter.Value);
break;
case FilterVoucherParameterKey.VoucherID:
query = query.Where(x => x.VoucherCode.Contains((string)searchParameter.Value));
break;
case FilterVoucherParameterKey.PointOfSale:
query = query.Where(x => x.PointOfSaleID == (byte)searchParameter.Value);
break;
case FilterVoucherParameterKey.Type:
query = query.Where(x => x.VoucherTypeID == (byte)searchParameter.Value);
break;
}
}
}
this.voucherResults = query.ToList();
}
}
Custom control
public static readonly DependencyProperty SearchCommandProperty =
DependencyProperty.Register("SearchCommand", typeof(ICommand), typeof(SearchControl));
public ICommand SearchCommand
{
get
{
return (ICommand)this.GetValue(SearchCommandProperty);
}
set
{
this.SetValue(SearchCommandProperty, value);
}
}
This is my dependency property so that I can bind the SearchItemsCommand to my Custom control.
Then I have another ICommand to execute the binded command and show the loading element in my custom control.
This LocalSearchCommand will be executed when you click on a button.
private DelegateCommand localSearchCommand;
public DelegateCommand LocalSearchCommand
{
get
{
if (this.localSearchCommand == null)
this.localSearchCommand = new DelegateCommand(this.LocalSearchCommandExecuted);
return this.localSearchCommand;
}
}
private void LocalSearchCommandExecuted()
{
loadingElement.Visible = true;
Task.Factory.StartNew(() =>
{
this.Dispatcher.Invoke((Action)(() => this.SearchCommand.Execute(null)));
})
.ContinueWith(t =>
{
if (t.IsCompleted)
{
t.Dispose();
}
});
}
The problem
I want to show a Loading element when the query is executing to interact with the user. To show this element, I have to set it visible.
The problem now is, when I set it visible and want to execute the search command, my whole UI freezes. After the result is fetched from the database and generated in the GridView, then, and only then, it shows my loading element. I do understand why this happens and I tried to solve it using a Task.
loadingElement.Visible = true;
Task.Factory.StartNew(() =>
{
this.Dispatcher.Invoke((Action)(() => this.SearchCommand.Execute(null)));
})
.ContinueWith(t =>
{
if (t.IsCompleted)
{
t.Dispose();
}
});
I have to use the Dispatcher in my Task to execute the SearchCommand, because it is owned by the UI-thread.
But because of the use of the Dispatcher class, I have the same problem as before. My loading element is only shown when the query is already executed, because the Dispatcher executes the search command back on the UI-thread.
Without the use of the Dispatcher class, it gives me the following error:
The calling thread cannot access this object because a different thread owns it.
I get this error on the line:
return (ICommand)this.GetValue(SearchCommandProperty);
Even with an empty SearchItemsCommandExecuted method, the error occurs.
What I already tried
I tried setting the TaskScheduler of the Task to
TaskScheduler.FromCurrentSynchronizationContext()
I used a lot of combinations of BeginInvoke and Invoke.
I tried to set the Visibility of the loading element in the Task.
But none of the above did work.
How can I solve my problem, so that the loading element is shown when the query is executing. Did I miss something obvious?
Thanks in advance!
Loetn
The problem is that you are creating a new Task with a ThreadPool thread, but using Dispatcher.Invoke, which runs your command on the UI Thread, hence why your UI is freezing.
You need to offload your SearchCommand work to a background thread, then update your UI with a Continuation on the UI Thread (Dont try updating your UI inside SearchCommand):
then, it shows my loading element. I do understand why this happens and I tried to solve it using a Task.
loadingElement.Visible = true;
Task.Factory.StartNew(() =>
{
return this.SearchCommand.Execute(null);
})
.ContinueWith(t =>
{
MyUIElement = t.Result; // Update your UI here.
}, TaskScheduler.FromCurrentSynchronizationContext());
Edit: Did not catch your binding of the first command to the second. So the following will proably not work. Looking into it...
EDIT 2: I assumed you want to start the background operation from your viewmodel. In the moment i can't think of another way than to make your loadingItem.Visible property a dependency property, move the background operation to your viewmodel, assign a property which is bound to loadingItem.Visible from there and remove the asynchronus stuff from your usercontrol.
You want to start your query on the background thread and assign the result to your ui thread:
private void LocalSearchCommandExecuted(object obj)
{
//can also be your loadingItem.
VisibleElement.Visibility = Visibility.Collapsed;
//get the ui context
var scheduler = TaskScheduler.FromCurrentSynchronizationContext();
Task.Factory.StartNew(() =>
{
//do your query on the background thread
LongRunningOperation();
})
//this happens on the ui thread because of the second parameter scheduler
.ContinueWith(t =>
{
if (t.IsCompleted)
{
VisibleElement.Visibility = Visibility.Visible;
//assign the result from the LongRunningOperation to your ui list
_list = new List<string>(_tempList);
//if you need to...
RaisePropertyChanged("SearchResults");
}
}, scheduler );
}
private void LongRunningOperation()
{
//assign your result to a temporary collection
//if you do not do that you will get an exception: An ItemsControl is inconsistent with its items source
_tempList = new List<string>();
for (int i = 0; i < 100; i++)
{
_tempList.Add("Test" + i);
Thread.Sleep(10);
}
}
I solved my problem with the help of this blog.
What I had to do is to edit the getter of my Dependency property SearchCommand so that it uses the Dispatcher.
public ICommand SearchCommand
{
get
{
return (ICommand)this.Dispatcher.Invoke(
System.Windows.Threading.DispatcherPriority.Background,
(DispatcherOperationCallback)delegate { return this.GetValue(SearchCommandProperty); },
SearchCommandProperty);
// Instead of this
// return this.GetValue(SearchCommandProperty);
}
set
{
this.SetValue(SearchCommandProperty, value);
}
}
And this is my Command method:
private void LocalSearchCommandExecuted()
{
this.loadingElement.Visible = true;
Task.Factory.StartNew(() =>
{
this.SearchCommand.Execute(null);
})
.ContinueWith(t =>
{
if (t.IsCompleted)
{
this.Dispatcher.BeginInvoke((Action)(() => this.loadingElement.Visible= false));
t.Dispose();
}
});
}
Thanks for all the help!

Best way to trigger OnClientCommand from server-side?

I've worked myself into a bit of a problem -- I'm not sure there's a graceful way out without restructuring my code. I have a server-side timer which is running and it needs to simulate clicking a tab of a RadTabStrip.
Client-Side I have the following method:
function OnClientTabSelected(sender, eventArgs)
{
FixSplitter($find(rightPaneID));
}
FixSplitter is dependent on an additional control, though:
function FixSplitter(sender, eventArgs)
{
var multiPage = $find(multiPageID);
...
}
now, server-side, I have the following:
public void DoTimerCycleTick(object sender, TimerEventArgs eventArgs)
{
GlobalSettings globalSettings = StateManager.GetStates<GlobalSettings>();
if( globalSettings.CycleEnabled )
{
if (!Equals(DateTime.Now.CompareTo(globalSettings.TimeLastCycled.AddMinutes(globalSettings.CycleInterval)), -1)) //CompareTo returns -1 when time is earlier than.
{
int nextIndex = SelectedIndex + 1;
if( nextIndex == Tabs.Count)
{
nextIndex = 0;
}
SelectedIndex = nextIndex;
LayoutManager.Instance.MultiPage.SelectedIndex = nextIndex;
LayoutManager.Instance.MultiPageUpdatePanel.Update();
//ScriptManager.RegisterClientScriptBlock(Page, Page.GetType(), "KEY", "OnClientTabSelected();", true);
globalSettings.TimeLastCycled = DateTime.Now;
}
}
StateManager.SaveGlobalSettings(globalSettings);
}
The relevant bit of this code is where I'm setting indices. Clearly, this does not trigger the OnClientTabSelected method. Yet, I need to run the FixSplitter method.
So, I thought a quick fix for this surprise would be to register a client script. Indeed, this would probably be sufficient if not for the dependence on the multiPage. Because my MultiPage is wrapped in an UpdatePanel which is currently updating -- it cannot be found using $find(multiPageID). $find(multiPageID) returns null where as $find(rightPaneID) returns the expected object -- both declarations are identical and the code works smoothly in all other scenarios. I am confident in saying that the culprit is the UpdatePanel.
Do I have options other than creating a new method which does the same things as OnClientTabSelected except forces the MultiPageUpdatePanel to postback after executing -- instead of calling Update on the UpdatePanel server-side?
I recognize that there are deeper issues with this, but it's unfinished code that can't be broken apart for a few more weeks.
EDIT: After some work, here's my proposed solution. Open to critique, I'm no expert in this. :)
/// <summary>
/// Performs one tick of a timer on the chart.
/// Ticks based on time for testability and to prevent
/// weird cases when skipping the clock time forward.
/// </summary>
public void DoTimerCycleTick(object sender, TimerEventArgs eventArgs)
{
GlobalSettings globalSettings = StateManager.GetStates<GlobalSettings>();
if( globalSettings.CycleEnabled )
{
if (!Equals(DateTime.Now.CompareTo(globalSettings.TimeLastCycled.AddMinutes(globalSettings.CycleInterval)), -1)) //CompareTo returns -1 when time is earlier than.
{
int oldIndex = SelectedIndex;
int newIndex = (oldIndex + 1) != Tabs.Count ? (oldIndex + 1) : 0;
SelectedIndex = newIndex;
LayoutManager.Instance.MultiPage.SelectedIndex = newIndex;
//LayoutManager.Instance.MultiPageUpdatePanel.Update();
ScriptManager.RegisterStartupScript(Page, Page.GetType(), "KEY", string.Format("OnServerTabSelected({0});", newIndex), true);
globalSettings.TimeLastCycled = DateTime.Now;
}
}
StateManager.SaveGlobalSettings(globalSettings);
}
var showLoadingPanel = true;
function OnServerTabSelected(newIndex) {
var oldID = $find(multiPageID).get_selectedPageView().get_id();
var newID = $find(multiPageID).get_pageViews().getPageView(newIndex).get_id();
if ($telerik.$("#" + oldID).is(":visible")) {
$telerik.$("#" + oldID).fadeOut(1000, function () {
$telerik.$("#" + newID).fadeIn(1000, function () {
showLoadingPanel = true;
$find(multiPageID).set_selectedIndex(newIndex);
});
});
showLoadingPanel = false;
FixSplitter($find(rightPaneID));
return;
}
}
Can't you just overload the FixSplitter method, one that takes those two paramters, does the find and then calls the other method with all 3 parameters? That way you can always override the multiPage value when you might need it;
function FixSplitter(sender, eventArgs)
{
var multiPage = $find(multiPageID);
FixSplitter(sender, multiPage, eventArgs);
}
function FixSplitter(sender, multiPage, eventArgs)
{
...
}
If you can't overload for some reason you could use an alternative notation like DoFixSplitter.

Categories

Resources