Updating label content within a method - c#

I need to change the WPF label content within a process,
I tried this but no content change in real time.
where am I doing wrong?
Event caller:
private void connect_button_MouseDown(object sender, MouseButtonEventArgs e)
{
Mouse.OverrideCursor = Cursors.Wait;
labelStstusUpdate("Connecting.."); // Status changer
config = new Configuration();
bool status = config.connectViaUSB();
Mouse.OverrideCursor = null;
if (!status)
{
labelStstusUpdate("Disconnected");// Status changer
}
else
{
labelStstusUpdate("Connected");// Status changer
}
}
Status changer method:
private void labelStstusUpdate(string message)
{
Dispatcher.BeginInvoke(DispatcherPriority.Background, (SendOrPostCallback)delegate
{
available_amount_label.Content = message;
}, null);
}

This is an code from my recent application where we are changing the value of label in runtime try to find a workaround from this
public partial class MainWindow : Window
{
int Value=0;
private delegate void UpdateMyLabel(System.Windows.DependencyProperty dp, Object value);
private void Processmerge()
{
UpdateMyLabel updateLabelDelegate = new UpdateMyLabel(_Mylabel.SetValue);
foreach (var item in Collections)
{
string _Mylabel= "Process completed..." + Value.ToString() + " %";
Dispatcher.Invoke(updateLabelDelegate, System.Windows.Threading.DispatcherPriority.Background, new object[] { System.Windows.Controls.Label.ContentProperty, _Mylabel});
Value++;
}
}
}
}

You cannot do that in WPF - the Databinding is totally different.
Basically, you have to set the Datacontext of the Window to your class and then bind the Label to a property on your class.
This would look like:
public class MyWindow()
{
public string Labeltext{ get; set; }
private void labelStstusUpdate(string message)
{
this.Labeltext = message
this.NotifyOfPropertyChange(() => this.Labeltext);
}
}
When you call the Notify Method, WPF will notice the change and update the label.
As a hint: Use a mvvm framework like Caliburn.Micro for WPF design, it drasticalls reduces the amount of errors and eases the development a bit.

Related

C# How to access label element (defined in designer) from a different class using background worker

I don't know how to acces a label element (myLabel) from a different class outside my Control Class while using background worker.
I really don't know the correct syntax.
Thanks a lot.
This is my code:
public partial class BasicControl : UserControl
{
// ...
private void bw_DoWork(object sender, DoWorkEventArgs e)
{
this.Invoke(new MethodInvoker(delegate { myLabel.Text = "THIS WORKS!"; }));
var moc = new MyOtherClass();
string result = moc.myMethod();
}
}
internal class MyOtherClass
{
public void myMethod()
{
myLabel.Text = "THIS DOES NOT WORK!"; // NOT WORKING
}
}
The DoWorkEventArgs class that you pass in your code is intended to allow interactions between the caller and the worker thread using its Argument property. In the following example it will be set to an instance of CustomDoWorkContext that implements INotifyPropertyChanged. Pass it when you start the worker bw_DoWork(sender, args: new DoWorkEventArgs(argument: context)) and have the worker pass args on to the method string result = moc.myMethod(args). This way, the outer, calling class receives PropertyChanged notifications while the thread is running whenever a bound property changes.
internal class MyOtherClass
{
internal string myMethod(DoWorkEventArgs args)
{
if (args.Argument is CustomDoWorkContext context)
{
context.Message = $"THIS WORKS! # {DateTime.Now} ";
}
return "Your result";
}
}
Here is the custom class that creates context for the worker thread.
class CustomDoWorkContext : INotifyPropertyChanged
{
public CustomDoWorkContext(CancellationToken cancellationToken)
{
Token = cancellationToken;
}
// This class can have as many bindable properties as you need
string _message = string.Empty;
public string Message
{
get => _message;
set => SetProperty(ref _message, value);
}
int _count = 0;
public int Count
{
get => _count;
set => SetProperty(ref _count, value);
}
public CancellationToken Token { get; }
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
protected bool SetProperty<T>( ref T backingStore, T value, [CallerMemberName] string propertyName = "")
{
if (EqualityComparer<T>.Default.Equals(backingStore, value)) return false;
backingStore = value;
OnPropertyChanged(propertyName);
return true;
}
}
The key point is to subscribe to the PropertyChanged event before starting the background worker.
// Get notified when any property changes in CustomDoWorkContext.
var context = new CustomDoWorkContext(cancellationToken: _cts.Token);
context.PropertyChanged += (sender, e) =>
{
switch (e.PropertyName)
{
case nameof(CustomDoWorkContext.Message):
Invoke((MethodInvoker)delegate
// When myMethod sets Message, change the label text.
{ myLabel.Text = $"{context.Message}"; });
break;
case nameof(CustomDoWorkContext.Count):
// When the count increments, change the checkbox text.
Invoke((MethodInvoker)delegate
{ checkBoxDoWork.Text = $"Count = {context.Count}"; });
break;
}
};
Then start the worker by passing the args (something like this).
// Set the `Argument` property when you start the worker.
// (Like this or something similar.)
Task.Run(() => bw_DoWork(sender, args: new DoWorkEventArgs(argument: context)));
Have the worker thread pass the args along to the myMethod:
private async void bw_DoWork(object sender, DoWorkEventArgs args)
{
var moc = new MyOtherClass();
if (args.Argument is CustomDoWorkContext context)
{
args.Cancel = context.Token.IsCancellationRequested;
while (!args.Cancel) // Loop for testing
{
context.Count++;
string result = moc.myMethod(args); // Pass the args
try { await Task.Delay(1000, context.Token); }
catch (TaskCanceledException) { return; }
}
}
}
TEST
BasicControl:UserControl with checkbox and label is placed on the MainForm.
Toggling the checkbox starts and stops the worker after instantiating CustomDoWorkContext.

Changing the icon in a button based off of MediaPlaybackState

I have a MediaPlayerElement and the following class to control it:
class MediaPlayer : INotifyPropertyChanged
{
private MediaPlaybackState _State;
public MediaPlaybackState State
{
get
{
return _State;
}
set
{
_State = value;
OnPropertyChanged();
}
}
public BitmapImage AlbumArt;
public string Title;
public string Subtitle;
private MediaPlayerElement mediaPlayer;
public event PropertyChangedEventHandler PropertyChanged = delegate { };
public void PlayerStateChanged(MediaPlaybackSession session, object sender)
{
State = session.PlaybackState;
}
public void SetMediaElement(MediaPlayerElement param)
{
mediaPlayer = param;
}
public void PlayFromSearchResult(SearchResult result)
{
AlbumArt = new BitmapImage();
AlbumArt.UriSource = new Uri(result.StationImage);
Title = result.StationName;
Subtitle = result.Subtext;
PlayFromRemoteM3U(result.StreamURL);
}
public void Pause()
{
mediaPlayer.MediaPlayer.Pause();
}
public void Play()
{
mediaPlayer.MediaPlayer.Play();
mediaPlayer.MediaPlayer.PlaybackSession.PlaybackStateChanged += PlayerStateChanged;
}
public async void PlayFromRemoteM3U(string url)
{
--SNIP--
mediaPlayer.Source = MediaSource.CreateFromUri(new Uri(streamDownloadUrl));
Play();
}
}
protected void OnPropertyChanged([CallerMemberName] string name = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
}
I would like to have a play/pause button that changes it's content based on the current player state, I'm currently debugging with a TextBlock to display the current state:
<TextBlock x:Name="media_player_state" x:FieldModifier="public" FontSize="20" Text="{x:Bind MediaPlayerInstance.State, Mode=OneWay}">Play</TextBlock>
When I run the application and start a stream so the state changes, I get the following error:
The application called an interface that was marshalled for a different thread. (Exception from HRESULT: 0x8001010E (RPC_E_WRONG_THREAD))
I want to know if there I am trying to accomplish this the right way or how to fix this.
Thanks
Because you're calling it from another thread, you need to use a dispatcher
public void PlayerStateChanged(MediaPlaybackSession session, object sender)
{
DispatcherHelper.ExecuteOnUIThreadAsync(() =>
{
State = session.PlaybackState;
});
}
please note 2 things:
1- DispatcherHelper is part of the windows community toolkit nuget package (https://www.nuget.org/profiles/Microsoft.Toolkit)
2- DispatcherHelper will be deprecated in favor of the better DispatcherQueueHelper in the 7.0 release
Because you are not setting the state everytime you Play() or Pause().
Change the _State value and then use OnpropertyChanged("State")

Update property on mainwindow even if a new window is opened in WPF

there is a propery on mainwindow of my app that is updated by a function taht runs in background (DoWork). BackgroundWorker is implemented in ViewModel. If I open an new page and comme back on the mainwindow this property takes automatically its default value with which it was initialized in the ViewModel constructor.
What should I do to keep this property updated even if a new window is opened?
public class ImageViewModel : INotifyPropertyChanged
{
private string currentData;
public ImageViewModel()
{
img = new ImageFile { path = "" };
currentData = "There is currently no update";
this.worker = new BackgroundWorker();
this.worker.DoWork += this.DoWork;
this.worker.ProgressChanged += this.ProgressChanged;
this.worker.RunWorkerCompleted += new RunWorkerCompletedEventHandler(worker_Completed);
this.worker.WorkerReportsProgress = true;
}
public string CurrentData
{
get { return this.currentData; }
private set
{
if (this.currentData != value)
{
this.currentData = value;
this.RaisePropertyChanged("CurrentData");
}
}
}
...
private void DoWork(object sender, DoWorkEventArgs e)
{
...
this.CurrentData = "file X is being updated...";
...
}
void worker_Completed(object sender, RunWorkerCompletedEventArgs e)
{
this.CurrentData = "There is currently no update...";
}
You can create a Singleton for your ViewModel like this:
Add this to your ViewModel class:
public static YourViewModelType Instance { get; set; }
In your Window.xaml.cs then assign the DataContext like this:
if(YourViewModel.Instance == null)
{
YourViewModel.Instance = new YourViewModelType();
}
this.DataContext = YourViewModel.Instance;
Note:
This will cause all your Windows to have the same DataContext. You should only do this if every Window needs the same Properties (Bindings) and stuff.
Otherwise I strongly recommend using different ViewModels per window.

How to raise an action through an interactivity trigger at an unittest?

i got some little problems. I want to write some unittest fora c#/wpf/interactivty project with visual studio 2010. and dont forget im a beginner, so sorry for that ;)
the unittest should simulate a (virtual) Key Down Event on a textbox and the result should raise an action.(Action result: Console output - just to check as first step)
i still fixed 2 problems -> the dispatcher problem & the presentationSource bug.
The unittest still simulates the keyevent and the keyevent reached the textbox but the question is, why the action not raised through the keydown event on the textbox?
It's a threading problem? what's my missunderstand?
here is the code
The Unittest
at the end of the unittest u could check the textbox - the keyboard works
[TestMethod]
public void simpleTest()
{
var mockWindow = new MockWindow();
//simple test to check if the virtualKeyboard works
string CheckText = "Checktext";
mockWindow.SendToUIThread(mockWindow.textbox, CheckText);
mockWindow.SendToUIThread(mockWindow.textbox, "k");
//needed to start the dispatcher
DispatcherUtil.DoEvents();
}
the Dispatcher fix
public static class DispatcherUtil
{
[SecurityPermissionAttribute(SecurityAction.Demand, Flags = SecurityPermissionFlag.UnmanagedCode)]
public static void DoEvents()
{
DispatcherFrame frame = new DispatcherFrame();
Dispatcher.CurrentDispatcher.BeginInvoke(DispatcherPriority.Background, new DispatcherOperationCallback(ExitFrame), frame);
Dispatcher.PushFrame(frame);
}
private static object ExitFrame(object frame)
{
((DispatcherFrame)frame).Continue = false;
return null;
}
}
My Testaction
class TestAction : TriggerAction<UIElement>
{
protected override void Invoke(object parameter)
{
Console.WriteLine("testAction invoke");
}
}
The MockWindow
public class MockWindow : Window
{
public TextBox textbox { get; private set; }
public MockWindow()
{
//add a grid&textbox
Grid grid = new Grid();
textbox = new TextBox();
this.Content = grid;
grid.Children.Add(textbox);
//create the testaction/triggerEvent & add them
TestAction testAction = new TestAction();
System.Windows.Interactivity.EventTrigger TestTrigger = new System.Windows.Interactivity.EventTrigger();
TestTrigger.EventName = "KeyDown";
TestTrigger.Actions.Add(testAction);
TestTrigger.Attach(this.textbox);
}
//enter a keyboard press on an UIElement
public void SendToUIThread(UIElement element, string text)
{
element.Dispatcher.BeginInvoke(new Action(() =>
{
SendKeys.Send(element, text);
}), DispatcherPriority.Input);
}
}
the MockKeyboard added from codeplex sendkeys + a presentationCore fix for unittest(added at class SendKeys)
public class FixPresentationSource : PresentationSource
{
protected override CompositionTarget GetCompositionTargetCore()
{
return null;
}
public override Visual RootVisual { get; set; }
public override bool IsDisposed { get { return false; } }
}

WPF: How to change the CurrentUICulture at runtime

I am trying to change the language my WPF app uses in a click event but it doesn't change.
private void menuItemGerman_Click(object sender, RoutedEventArgs e)
{
Settings.Default.Culture = "de-DE";
Thread.CurrentThread.CurrentCulture = new CultureInfo(Settings.Default.Culture);
Thread.CurrentThread.CurrentUICulture = new CultureInfo(Settings.Default.Culture);
}
What am I missing?
What am I missing?
You changed the culture registered with the thread, and String.Format will use this now, but you need to reload all localized items in the WPF hierarchy.
WPF Localization – On-the-fly Language Selection has more information.
If you have resource files, e.g.:
Resources.resx
Resources.hu-hu.resx
... and want to change the localization at runtime,
... and do not want to mess with additional resource dictionaries and recoding all UI localizations,
it will work with the
Thread.CurrentThread.CurrentUICulture = new CultureInfo(lang);
But it will not change the already shown window's language.
To achieve that, more coding is required - the Application lifecycle must be managed, instead of the default.
First, remove the StartupUri from App.xaml:
<Application
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
x:Class="ADUI.App"
xmlns:System="clr-namespace:System;assembly=mscorlib" >
<!--StartupUri="wndMain.xaml">-->
<Application.Resources>
</Application.Resources>
Second, implement a class, which is now responsible for the application lifecycle:
public class LocApp: Application
{
[STAThread]
public static void Main()
{
App app = new App();
app.ShutdownMode = ShutdownMode.OnExplicitShutdown;
wndMain wnd = new wndMain();
wnd.Closed += Wnd_Closed;
app.Run(wnd);
}
private static void Wnd_Closed(object sender, EventArgs e)
{
wndMain wnd = sender as wndMain;
if (!string.IsNullOrEmpty(wnd.LangSwitch))
{
string lang = wnd.LangSwitch;
wnd.Closed -= Wnd_Closed;
Thread.CurrentThread.CurrentUICulture = new CultureInfo(lang);
wnd = new wndMain();
wnd.Closed += Wnd_Closed;
wnd.Show();
}
else
{
App.Current.Shutdown();
}
}
}
Do not forget to change the startup object on your Project properties / Application page to LocApp!
Finally, implement some code which switches the languages in the main window's code:
public partial class wndMain : Window
{
public string LangSwitch { get; private set; } = null;
// ... blah, blah, blah
private void tbEn_MouseLeftButtonUp(object sender, MouseButtonEventArgs e)
{
LangSwitch = "en";
Close();
}
private void tbHu_MouseLeftButtonUp(object sender, MouseButtonEventArgs e)
{
LangSwitch = "hu-hu";
Close();
}
// ... blah, blah, blah
}
Make sure, that the provided localization code matches with one of the resx file language code ("hu-hu" in this example)!
This solution will close and reopen the main window, with the chosen language, and will exit if the main window closed by other means.
I also experienced this problem and my solution was:
Resources.en-US
Resources.pt-PT
I created a class that will return a dictionary with the key and the label:
public class Labels : ObservableObject
{
public Dictionary<string, string> Items { get; set; }
public string this[string name]
{
get
{
return Items.ContainsKey(name) ? Items[name] : "";
}
}
public Labels()
{
Items = new Dictionary<string, string>();
}
}
Next, one more class to get the Resources:
public static class LanguageUtils
{
public static Labels GetLangLables(string label)
{
var resources = Resources.ResourceManager.GetResourceSet(new CultureInfo(label), true, true);
return new Labels
{
Items = resources.Cast<DictionaryEntry>().ToDictionary(r => r.Key.ToString(), r => r.Value.ToString())
};
}
}
When you need some language:
LanguageUtils.GetLangLables("pt-PT");
Once, you can't raise (RaisePropertyChanged()) static properties, use this:
public class LanguageContext
{
private static LanguageContext _languageContext;
public static LanguageContext Instance
{
get
{
if (_languageContext == null)
{
_languageContext = new LanguageContext();
}
return _languageContext;
}
}
protected LanguageContext()
{
CurrentLangLabels = LanguageUtils.GetLangLables("en-US");
}
public Labels CurrentLangLabels { get; set; }
}
Now you can update language:
LanguageContext.Instance.CurrentLangLabels = LanguageUtils.GetLangLables(SelectedLanguage.Resource);
Raise like this:
public Labels CurrentLangLabels
{
get { return LanguageContext.Instance.CurrentLangLabels; }
set { RaisePropertyChanged(); }
}
And use label:
CurrentLangLabels.Items[LabelName]
this might come in handy for someone. I have used the advice given by George & Chris Schaller above to make this work in my project without creating new Application class.
public string LangSwitch { get; private set; } = null;
private void BtnLngPl_Click(object sender, RoutedEventArgs e)
{
CultureInfo current = CultureInfo.CurrentUICulture;
CultureInfo newUiCulture;
if (current.Name.Equals("en-US"))
{
newUiCulture = new CultureInfo("pl");
Application.Current.ShutdownMode = ShutdownMode.OnExplicitShutdown;
if (System.Windows.Application.Current.MainWindow != null)
((MainWindow)System.Windows.Application.Current.MainWindow).Closed += Wnd_Closed;
Thread.CurrentThread.CurrentCulture = newUiCulture;
Thread.CurrentThread.CurrentUICulture = newUiCulture;
LangSwitch = "pl";
Close();
}
else
newUiCulture = new CultureInfo("en-US");
CultureInfo.CurrentUICulture = newUiCulture;
Console.WriteLine(#"The current UI culture is now {0}",
CultureInfo.CurrentUICulture.Name);
}
and the other necessary code can be taken from George's answer. Not sure how good of a solution this is, but does the job for me.

Categories