Update textblock to value of variable in WPF - c#

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);
}
}

Related

UWP C# MVVM How To Access ViewModel from Other Page

I am tying to further understand MVVM with some example scenario. I have a rootpage with a 'maindisplay' textblock. I would like to display 'status' or 'scenarios' from activation of any form of UI eg. togglebutton on the 'maindisplay' textblock.
I am able to bind the the page navigation info in the rootpageviewmodel to the textblock. However, I am not able to achieve the result when displaying info from different page.
I have checked another post multiple-viewmodels-in-same-view & Accessing a property in one ViewModel from another it's quite similar but it didn't work.
Please help. Thanks.
While accessing the RootPageViewModel should retain the instance?
View
<TextBlock Text="{x:Bind RootViewModel.MainStatusContent, Mode=OneWay}"/>
RootPage.xaml.cs
public sealed partial class RootPage : Page
{
private static RootPage instance;
public RootPageViewModel RootViewModel { get; set; }
public RootPage()
{
RootViewModel = new RootPageViewModel();
this.InitializeComponent();
// Always use the cached page
this.NavigationCacheMode = NavigationCacheMode.Required;
}
public static RootPage Instance
{
get
{
if (instance == null)
{
instance = new RootPage();
}
return instance;
}
}
private void nvTopLevelNav_ItemInvoked(NavigationView sender, NavigationViewItemInvokedEventArgs args)
{
if (args.IsSettingsInvoked)
{
contentFrame.Navigate(typeof(SettingsPage));
RootViewModel.MainStatusContent = "Settings_Page";
}
else
{
var navItemTag = args.InvokedItemContainer.Tag.ToString();
RootViewModel.MainStatusContent = navItemTag;
switch (navItemTag)
{
case "Home_Page":
contentFrame.Navigate(typeof(HomePage));
break;
case "Message_Page":
contentFrame.Navigate(typeof(MessagePage));
break;
}
}
}
}
RootPage ViewModel:
public class RootPageViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private static RootPageViewModel instance = new RootPageViewModel();
public static RootPageViewModel Instance
{
get
{
if (instance == null)
instance = new RootPageViewModel();
return instance;
}
}
public RootPageViewModel()
{
}
private string _mainStatusContent;
public string MainStatusContent
{
get
{
return _mainStatusContent;
}
set
{
_mainStatusContent = value;
OnPropertyChanged();
}
}
protected void OnPropertyChanged([CallerMemberName] string name = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
}
}
MessagePage.xaml.cs - to access RootPage ViewModel
public sealed partial class MessagePage : Page
{
public MessagePageViewModel MessageViewModel { get; set; }
public MessagePage()
{
MessageViewModel = new MessagePageViewModel();
this.InitializeComponent();
// Always use the cached page
this.NavigationCacheMode = NavigationCacheMode.Required;
}
private void Message1_Checked(object sender, RoutedEventArgs e)
{
RootPageViewModel.Instance.MainStatusContent = "Message 1 Selected";
}
private void Message1_Unchecked(object sender, RoutedEventArgs e)
{
RootPageViewModel.Instance.MainStatusContent = "Message 1 De-Selected";
}
}
When I debug the value did write to the instance but did't update the TextBlock. Did I do anything wrong in my XAML binding?
UWP C# MVVM How To Access ViewModel from Other Page
The better way is make static variable for RootPage, but not make singleton instance for RootPage and RootPageViewModel.
For example:
public RootPage ()
{
this.InitializeComponent();
this.NavigationCacheMode = NavigationCacheMode.Required;
Instance = this;
RootViewModel = new RootPageViewModel();
}
public static RootPage Instance;
Usage
private void Message1_Checked(object sender, RoutedEventArgs e)
{
RootPage.Instance.RootViewModel.MainStatusContent = "Message 1 Selected";
}
private void Message1_Unchecked(object sender, RoutedEventArgs e)
{
RootPage.Instance.RootViewModel.MainStatusContent = "Message 1 De-Selected";
}

DisplayAlert not showing during changes in variable

Main Page
public partial class MainPage : ContentPage
{
public MainPage()
{
InitializeComponent();
NavigationPage.SetHasNavigationBar(this, false);
btnStartGame.Clicked += btnStartGame_Clicked;
}
public async void btnStartGame_Clicked(object sender, EventArgs e)
{
GlobalVariables globalVar = new GlobalVariables();
globalVar.CurrentSeconds = 20;
StartPage startPage = new StartPage();
startPage.setGlobalVariables(globalVar);
await Navigation.PushAsync(startPage);
}
}
Start Page
public partial class StartPage : ContentPage
{
GlobalVariables globalVar;
public StartPage()
{
InitializeComponent();
this.BindingContext = globalVar;
NavigationPage.SetHasNavigationBar(this, false);
}
public void setGlobalVariables(GlobalVariables globalVar)
{
this.globalVar = globalVar;
}
private void btnSample_Clicked(object sender, System.EventArgs e)
{
globalVar.CurrentSeconds++;
DisplayAlert("AW", globalVar.CurrentSeconds.ToString(), "AW");
}
}
GlobalVariables.cs
public class GlobalVariables : INotifyPropertyChanged
{
private int _currentSeconds;
public int CurrentSeconds
{
get { return _currentSeconds; }
set
{
if (_currentSeconds != value)
{
_currentSeconds = value;
Device.BeginInvokeOnMainThread(async () =>
{
await FingerSmash2.App.Current.MainPage.DisplayAlert("AW", "AW", "AW");
});
}
}
}
public event PropertyChangedEventHandler PropertyChanged;
public void OnPropertyChanged([CallerMemberName] string name = "")
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
}
}
With this codes, every time btnSample_Clicked runs, the set{} in CurrentSeconds will also fire. But the problem is, the DisplayAlert inside set{} does not fire at all, only the DisplayAlert inside btnSample_Clicked.
How to also fire the DisplayAlert inside set{}? Or if not possible, is there a way to fire an event in Start Page from GlobalVariables?
Your code seems fine.
As described in Xamarin Live Player iOS DisplayActionSheet/Alert it may be related to the Xamarin Live Player.
Deploying your app on an device or even the emulator should ensure if your code is correct or not !

Using a Stopwatch and DataBinding on a WPF Window that is already being updated using IProgress

In my Main() WPF program I run a time consuming method asynchronously. When this method is running, I fire up a secondary window that contains a ProgressBar, which I update using IProgress.
Following is an example of my setup.
MAIN Program:
public partial class MainWindow : Window
{
private ProgressBarWindow pbwWindow = null;
public MainWindow()
{
InitializeComponent();
}
private void RunMethodAsync(IProgress<int> progress)
{
Dispatcher.Invoke(() =>
{
pbwWindow = new ProgressBarWindow("Processing...");
pbwWindow.Owner = this;
pbwWindow.Show();
});
TimeConsumingMethod(progress);
}
private void TimeConsumingMethod(IProgress<int> progress)
{
for (int i = 1; i <= 100; i++)
{
// Thread.Sleep() represents actual time consuming work being done.
Thread.Sleep(100);
progress.Report(i);
}
}
private async void btnRun_Click(object sender, RoutedEventArgs e)
{
IProgress<int> progress;
progress = new Progress<int>(i => pbwWindow.SetProgressUpdate(i));
await Task.Run(() => RunMethodAsync(progress));
}
}
My ProgressBarWindow which contains the progress bar looks like this:
public partial class ProgressBarWindow : Window
{
Stopwatch stopwatch = new Stopwatch();
BackgroundWorker worker = new BackgroundWorker();
public string ElapsedTimeString { get; set; }
public ProgressBarWindow(string infoText)
{
InitializeComponent();
SetTimer();
}
private void Window_Loaded(object sender, RoutedEventArgs e)
{
StartTimer();
}
private void SetTimer()
{
worker.WorkerReportsProgress = true;
worker.WorkerSupportsCancellation = true;
worker.DoWork += (s, e) =>
{
while (!worker.CancellationPending)
{
worker.ReportProgress(0, stopwatch.Elapsed);
Thread.Sleep(1000);
}
};
worker.ProgressChanged += (s, e) =>
{
TimeSpan elapsedTime = (TimeSpan)e.UserState;
ElapsedTimeString = string.Format("{0}:{1}:{2}", elapsedTime.Minutes, elapsedTime.Seconds, elapsedTime.Milliseconds);
};
}
private void StartTimer()
{
stopwatch.Start();
worker.RunWorkerAsync();
}
private void StopTimer()
{
stopwatch.Stop();
worker.CancelAsync();
}
public void SetProgressUpdate(int progress)
{
pbLoad.Value = progress;
if (progress >= 100)
{
StopTimer();
Close();
}
}
}
I borrowed the StopWatch logic from this SO answer.
Then, on my ProgressBarWindow I have a TextBlock which I've used Binding as follows, just as the answer above says.
<TextBlock Name="tbElapsedTime" Text="{Binding ElapsedTimeString}"/>
Now when I run the program, the method executes, and the progress bar updates just fine. However, my TextBlock that's supposed to update with the elapsed time does not get updated.
To verify my timer's running fine, I updated TextBlock value directly as follows instead of Binding and it worked as expected and displayed Elapsed Time:
worker.ProgressChanged += (s, e) =>
{
TimeSpan elapsedTime = (TimeSpan)e.UserState;
ElapsedTimeString = string.Format("{0}:{1}:{2}", elapsedTime.Minutes, elapsedTime.Seconds, elapsedTime.Milliseconds);
tbElapsedTime.Text = ElapsedTimeString;
};
So I'm guessing my problem is with the Binding and possibly using BackgroundWorker on a windows that's already being run asynchronously? How could I fix this so I could use DataBinding?
As mentioned by Ginger Ninja, you have to implement INotifyPropertyChanged and use RelativeSource={RelativeSource Self} (as additional setting to the binding):
public partial class MainWindow : Window, INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private string _ElapsedTimeString;
public string ElapsedTimeString
{
get { return _ElapsedTimeString; }
set
{
if (_ElapsedTimeString != value)
{
_ElapsedTimeString = value;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("ElapsedTimeString"));
}
}
}
// ....
}
and the XAML:
<TextBlock Name="tbElapsedTime" Text="{Binding ElapsedTimeString, RelativeSource={RelativeSource Self}}"/>
Data binding is often used in combination with MVVM. That is IMHO the prefered way to solve your problem... If you want to use MVVM, you have to implement a view model that contains all the logic and implements INotifyPropertyChanged. Than you can simply bind properties from the view model to the view. That ensures a nice separation between (GUI related) logic and view.

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.

C# Custom Event In Class not updating Textbox

I have an issue with my custom event not updating a text box on my UWP application. If I replace the Textbox1.Text with debug.Writeline it works. Is there a reason why I can't update a textbox using an event? If I use the Progress object it works. I am just trying to figure out why it wouldnt work with my own custom event. Thank you
public sealed partial class MainPage : Page
{
public MainPage()
{
this.InitializeComponent();
}
private void button_click(object sender, RoutedEventArgs e)
{
recData myRecDataobject = new recData();
myRecDataobject.dataRecvEvent += () =>
{
textBox2.Text = "Event Occured"; // This throws an error
Debug.WriteLine("test2");
};
Progress<int> progress = new Progress<int>();
myRecDataobject.getDataMethodAsync(progress);
progress.ProgressChanged += (o, result) =>
{
textBox1.Text = result.ToString();
};
}
}
public class recData
{
public delegate void myEvenetHandlerDelegate();
public event myEvenetHandlerDelegate dataRecvEvent;
private int _myValue;
public int myValue
{
get
{
return _myValue;
}
set
{
_myValue = value;
}
}
public async void getDataMethodAsync(Progress<int> progress)
{
await getDataMethod(progress);
}
private Task getDataMethod(IProgress<int> progress)
{
return Task.Factory.StartNew(() =>
{
for (int i = 0; i < 1000; i++)
{
Task.Delay(2000).Wait();
if (dataRecvEvent != null)
{
dataRecvEvent();
progress.Report(i);
}
}
});
}
}
You are trying to update a XAML property from a background thread. This doesn't work (your error should be "access denied").
Use Dispatcher.BeginInvoke to schedule the TextBox property update on the UI thread.

Categories