Mon code est comme ci-dessous
public CountryStandards()
{
InitializeComponent();
try
{
FillPageControls();
}
catch (Exception ex)
{
MessageBox.Show(ex.Message, "Country Standards", MessageBoxButton.OK, MessageBoxImage.Error);
}
}
/// <summary>
/// Fills the page controls.
/// </summary>
private void FillPageControls()
{
popUpProgressBar.IsOpen = true;
lblProgress.Content = "Loading. Please wait...";
progress.IsIndeterminate = true;
worker = new BackgroundWorker();
worker.DoWork += new System.ComponentModel.DoWorkEventHandler(worker_DoWork);
worker.ProgressChanged += new System.ComponentModel.ProgressChangedEventHandler(worker_ProgressChanged);
worker.WorkerReportsProgress = true;
worker.WorkerSupportsCancellation = true;
worker.RunWorkerCompleted += new System.ComponentModel.RunWorkerCompletedEventHandler(worker_RunWorkerCompleted);
worker.RunWorkerAsync();
}
private void worker_DoWork(object sender, System.ComponentModel.DoWorkEventArgs e)
{
GetGridData(null, 0); // filling grid
}
private void worker_ProgressChanged(object sender, System.ComponentModel.ProgressChangedEventArgs e)
{
progress.Value = e.ProgressPercentage;
}
private void worker_RunWorkerCompleted(object sender, System.ComponentModel.RunWorkerCompletedEventArgs e)
{
worker = null;
popUpProgressBar.IsOpen = false;
//filling Region dropdown
Standards.UDMCountryStandards objUDMCountryStandards = new Standards.UDMCountryStandards();
objUDMCountryStandards.Operation = "SELECT_REGION";
DataSet dsRegionStandards = objStandardsBusinessLayer.GetCountryStandards(objUDMCountryStandards);
if (!StandardsDefault.IsNullOrEmptyDataTable(dsRegionStandards, 0))
StandardsDefault.FillComboBox(cmbRegion, dsRegionStandards.Tables[0], "Region", "RegionId");
//filling Currency dropdown
objUDMCountryStandards = new Standards.UDMCountryStandards();
objUDMCountryStandards.Operation = "SELECT_CURRENCY";
DataSet dsCurrencyStandards = objStandardsBusinessLayer.GetCountryStandards(objUDMCountryStandards);
if (!StandardsDefault.IsNullOrEmptyDataTable(dsCurrencyStandards, 0))
StandardsDefault.FillComboBox(cmbCurrency, dsCurrencyStandards.Tables[0], "CurrencyName", "CurrencyId");
if (Users.UserRole != "Admin")
btnSave.IsEnabled = false;
}
/// <summary>
/// Gets the grid data.
/// </summary>
/// <param name="sender">The sender.</param>
/// <param name="pageIndex">Index of the page.( used in case of paging) </pamam>
private void GetGridData(object sender, int pageIndex)
{
Standards.UDMCountryStandards objUDMCountryStandards = new Standards.UDMCountryStandards();
objUDMCountryStandards.Operation = "SELECT";
objUDMCountryStandards.Country = txtSearchCountry.Text.Trim() != string.Empty ? txtSearchCountry.Text : null;
DataSet dsCountryStandards = objStandardsBusinessLayer.GetCountryStandards(objUDMCountryStandards);
if (!StandardsDefault.IsNullOrEmptyDataTable(dsCountryStandards, 0) && (chkbxMarketsSearch.IsChecked == true || chkbxBudgetsSearch.IsChecked == true || chkbxProgramsSearch.IsChecked == true))
{
DataTable objDataTable = StandardsDefault.FilterDatatableForModules(dsCountryStandards.Tables[0], "Country", chkbxMarketsSearch, chkbxBudgetsSearch, chkbxProgramsSearch);
dgCountryList.ItemsSource = objDataTable.DefaultView;
}
else
{
MessageBox.Show("No Records Found", "Country Standards", MessageBoxButton.OK, MessageBoxImage.Information);
btnClear_Click(null, null);
}
}
L’étape objUDMCountryStandards.Country = txtSearchCountry.Text.Trim() != string.Empty ? txtSearchCountry.Text : null;
dans l’obtention des données de la grille lève une exception
Le thread appelant ne peut pas accéder à cet objet car un autre thread le possède.
Quel est le problème ici?
C'est un problème fréquent chez les personnes qui commencent. Chaque fois que vous mettez à jour vos éléments d'interface utilisateur à partir d'un thread autre que le thread principal, vous devez utiliser:
this.Dispatcher.Invoke(() =>
{
...// your code here.
});
Vous pouvez également utiliser control.Dispatcher.CheckAccess()
pour vérifier si le thread actuel est propriétaire du contrôle. S'il en est le propriétaire, votre code a l'air normal. Sinon, utilisez le motif ci-dessus.
Un autre bon usage de Dispatcher.Invoke
consiste à mettre immédiatement à jour l'interface utilisateur dans une fonction effectuant d'autres tâches:
// Force WPF to render UI changes immediately with this magic line of code...
Dispatcher.Invoke(new Action(() => { }), DispatcherPriority.ContextIdle);
J'utilise ceci pour mettre à jour le texte du bouton en "Processing ..." et le désactiver lors des requêtes WebClient
.
Pour ajouter mes 2 centimes, l’exception peut se produire même si vous appelez votre code par le biais de System.Windows.Threading.Dispatcher.CurrentDispatcher.Invoke()
.
Le fait est que vous devez appeler Invoke()
de la Dispatcher
du contrôle auquel vous essayez d'accéder , qui dans certains cas peut différer de System.Windows.Threading.Dispatcher.CurrentDispatcher
. Vous devez donc utiliser YourControl.Dispatcher.Invoke()
pour être sûr. Je me suis cogné la tête pendant quelques heures avant de m'en rendre compte.
Pour les futurs lecteurs, il semble que cela ait changé dans les nouvelles versions de .NET (4.0 et versions ultérieures). Désormais, vous n'avez plus à vous préoccuper du bon répartiteur lors de la mise à jour des propriétés de sauvegarde de l'interface utilisateur de votre VM. Le moteur WPF organisera des appels cross-thread sur le thread d'interface utilisateur correct. Voir plus de détails ici . Merci à @aaronburro pour l'info et le lien. Vous voudrez peut-être aussi lire notre conversation ci-dessous dans les commentaires.
Si vous rencontrez ce problème et que des contrôles d'interface utilisateur ont été créés sur un thread travailleur séparé lorsque vous utilisez BitmapSource
ou ImageSource
dans WPF, appelez d'abord la méthode Freeze()
avant de transmettre le message. BitmapSource
ou ImageSource
en tant que paramètre de toute méthode. L'utilisation de Application.Current.Dispatcher.Invoke()
ne fonctionne pas dans de tels cas
c'est arrivé avec moi parce que j'ai essayé de access UI
composant dans another thread insted of UI thread
comme ça
private void button_Click(object sender, RoutedEventArgs e)
{
new Thread(SyncProcces).Start();
}
private void SyncProcces()
{
string val1 = null, val2 = null;
//here is the problem
val1 = textBox1.Text;//access UI in another thread
val2 = textBox2.Text;//access UI in another thread
localStore = new LocalStore(val1);
remoteStore = new RemoteStore(val2);
}
pour résoudre ce problème, envelopper tout appel d'interface utilisateur à l'intérieur ce que Candide a mentionné plus haut dans sa réponse
private void SyncProcces()
{
string val1 = null, val2 = null;
this.Dispatcher.Invoke((Action)(() =>
{//this refer to form in WPF application
val1 = textBox.Text;
val2 = textBox_Copy.Text;
}));
localStore = new LocalStore(val1);
remoteStore = new RemoteStore(val2 );
}
Pour une raison quelconque, la réponse de Candide n'a pas été construite. C'était utile, cependant, car cela m'a amené à trouver ceci qui a parfaitement fonctionné:
System.Windows.Threading.Dispatcher.CurrentDispatcher.Invoke((Action)(() =>
{
//your code here...
}));
Vous devez mettre à jour l'interface utilisateur, utilisez donc
Dispatcher.BeginInvoke(new Action(() => {GetGridData(null, 0)}));
J'ai aussi constaté que System.Windows.Threading.Dispatcher.CurrentDispatcher.Invoke()
n'est pas toujours un répartiteur de contrôle de cible, tout comme dotNet l'a écrit dans sa réponse. Je n'avais pas accès au propre répartiteur du contrôle, j'ai donc utilisé Application.Current.Dispatcher
et le problème a été résolu.
Le problème est que vous appelez GetGridData
à partir d'un thread d'arrière-plan. Cette méthode accède à plusieurs contrôles WPF liés au thread principal. Toute tentative d'y accéder à partir d'un thread d'arrière-plan entraînera cette erreur.
Pour revenir au bon fil, utilisez SynchronizationContext.Current.Post
. Cependant, dans ce cas particulier, il semble que la majorité du travail que vous effectuez repose sur l'assurance-chômage. Par conséquent, vous créez un thread d'arrière-plan juste pour revenir immédiatement au thread d'interface utilisateur et effectuer un travail. Vous devez modifier un peu votre code pour qu'il puisse effectuer le travail coûteux sur le thread d'arrière-plan, puis poster les nouvelles données sur le thread d'interface utilisateur.
Cela fonctionne pour moi.
new Thread(() =>
{
Thread.CurrentThread.IsBackground = false;
Application.Current.Dispatcher.BeginInvoke(DispatcherPriority.Background, (SendOrPostCallback)delegate {
//Your Code here.
}, null);
}).Start();
Comme mentionné ici , Dispatcher.Invoke
pourrait geler l'interface utilisateur. Devrait utiliser Dispatcher.BeginInvoke
à la place.
Voici une classe d’extension pratique pour simplifier la vérification et l’appel du répartiteur d’appel.
Exemple d'utilisation: (appel depuis la fenêtre WPF)
this Dispatcher.InvokeIfRequired(new Action(() =>
{
logTextbox.AppendText(message);
logTextbox.ScrollToEnd();
}));
Classe d'extension:
using System;
using System.Windows.Threading;
namespace WpfUtility
{
public static class DispatcherExtension
{
public static void InvokeIfRequired(this Dispatcher dispatcher, Action action)
{
if (dispatcher == null)
{
return;
}
if (!dispatcher.CheckAccess())
{
dispatcher.BeginInvoke(action, DispatcherPriority.ContextIdle);
return;
}
action();
}
}
}
J'ai continué à avoir l'erreur lorsque j'ai ajouté des combobox en cascade à mon application WPF et j'ai résolu l'erreur en utilisant cette API:
using System.Windows.Data;
private readonly object _lock = new object();
private CustomObservableCollection<string> _myUiBoundProperty;
public CustomObservableCollection<string> MyUiBoundProperty
{
get { return _myUiBoundProperty; }
set
{
if (value == _myUiBoundProperty) return;
_myUiBoundProperty = value;
NotifyPropertyChanged(nameof(MyUiBoundProperty));
}
}
public MyViewModelCtor(INavigationService navigationService)
{
// Other code...
BindingOperations.EnableCollectionSynchronization(AvailableDefectSubCategories, _lock );
}
Pour plus de détails, veuillez consulter https://msdn.Microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k (System.Windows.Data.BindingOperations.EnableCollectionSynchronization); k (TargetFrameworkMoniker-). .NETFramework, version% 3Dv4.7); k (DevLang-csharp) & rd = true
En outre, une autre solution consiste à s'assurer que vos contrôles sont créés dans un thread d'interface utilisateur, et non par un thread de travail en arrière-plan, par exemple.