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 !
Related
To pass data beetwen viewmodels when navigating I use query parameters (IQueryAttributable), i.e.:
NavigationParameters[nameof(SomeProperty)] = SomeProperty;
await Shell.Current.GoToAsync("SomePage", NavigationParameters);
It works as it should be working, but I wish to put SomePage into a TabBar:
<TabBar>
<ShellContent Route="SomePage"
ContentTemplate="{DataTemplate local:SomePage}"/>
...
</TabBar>
Is there a way to pass data when user click/tap SomePage icon on the tab bar? Is there some event for that so I could hook up GoToAsync method? Or maybe there is another way than query to pass data beetwen viewmodels?
MauiProgram.cs:
public static class MauiProgram
{
public static MauiApp CreateMauiApp()
{
var builder = MauiApp.CreateBuilder();
builder.UseMauiApp<App>();
builder.Services
.AddSingleton<Page_1>
.AddSingleton<ViewModel_1>
.AddSingleton<Page_2>
.AddSingleton<ViewModel_2>
.AddSingleton<ISharedDataInterface, SharedDataInterfaceImplementation>();
return builder.Build();
}
}
Page_1.xaml.cs:
public partial class Page_1 : ContentPage
{
public LogoPage(ViewModel_1 viewModel_1)
{
BindingContext = viewModel_1;
InitializeComponent();
}
}
Page_2.xaml.cs:
public partial class Page_2 : ContentPage
{
public LogoPage(ViewModel_2 viewModel_2)
{
BindingContext = viewModel_2;
InitializeComponent();
}
}
ViewModel_1.cs:
public class ViewModel_1
{
public ViewModel_1(ISharedDataInterface sharedDataInterfaceImplementation)
{
...
}
}
ViewModel_2.cs:
public class ViewModel_2
{
public ViewModel_2(ISharedDataInterface sharedDataInterfaceImplementation)
{
...
}
}
If the two pages are in the same Tab,you can create a static global variable for the viewmodel in App.xaml.cs and access this variable in your different pages.
For example,we can first define a view model (e.g. TestViewModel.cs):
public class TestViewModel: INotifyPropertyChanged
{
public TestViewModel() {
str = "123";
}
public string _str;
public string str
{
get
{
return _str;
}
set
{
_str = value;
OnPropertyChanged("str");
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = "")
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
And define a static global variable in App.xaml.cs:
public partial class App : Application
{
public static TestViewModel testViewModel;
public App()
{
InitializeComponent();
MainPage = new AppShell();
testViewModel = new TestViewModel();
}
}
Then , you can assgin the global variable for BindingContext in different pages:
public partial class Tab1 : ContentPage
{
public Tab1()
{
InitializeComponent();
BindingContext = App.testViewModel;
}
}
public partial class Tab2 : ContentPage
{
public Tab2()
{
InitializeComponent();
BindingContext = App.testViewModel;
}
private void Button_Clicked(object sender, EventArgs e)
{
App.testViewModel.str = "Test";
}
}
"You could also hold handles to other view models in your view model, like a common section that's shared between all your pages."
That is the advice to follow.
Suppose each tab's viewmodel has public MySharedType Shared { get; private set; },
and constructor is MyTab1ViewModel(MySharedType shared) { this.Shared = shared; }.
Then from xaml, access a shared property by: {Binding Shared.MyProperty1}.
Let's see a practical example.
You have one TabbedPage and two ContentPage. The idea is that you will pass the correct parameter when the user taps the specific tab. In this context you can pass any parameter on demand to your tab page.
<?xml version="1.0" encoding="utf-8" ?>
<TabbedPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="MyApp.Pages.TabPage"
xmlns:pages="clr-namespace:MyApp.Pages">
<pages:Page1/>
<pages:Page2/>
</TabbedPage>
Add the bellow code to the TabbedPage code behind.
public TabPage()
{
InitializeComponent();
this.CurrentPageChanged += TabPage_CurrentPageChanged;
PassParams();
}
private void TabPage_CurrentPageChanged(object sender, EventArgs e)
{
PassParams();
}
private void PassParams()
{
if (this.CurrentPage.GetType() == typeof(Page1))
{
Page1 cur = (Page1)this.CurrentPage;
cur.Param1 = 123;
}
if (this.CurrentPage.GetType() == typeof(Page2))
{
Page2 cur = (Page2)this.CurrentPage;
cur.Param2 = 456;
}
}
Add the below to Page1 code behind. About the same code goes to Page2, just replace number 1.
public Page1()
{
InitializeComponent();
}
int param1;
public int Param1
{
set
{
param1 = value;
OnPropertyChanged();
BindingContext = new Page1ViewModel(param1);
}
}
The Page1ViewModel will have to handle the parameter.
public Page1ViewModel(int param1)
{
//populate your view with your models
}
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";
}
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);
}
}
I am trying to pass strings between forms. Why does it not? Am I missing something or is it an error in the program or what?
On UserControl3
UserControl1 u1;
public UserControl3()
{
u1 = new UserControl1();
InitializeComponent();
}
On UserControl3
public void materialCheckBox1_CheckedChanged(object sender, EventArgs e)
{
if (materialCheckBox1.Checked)
{
u1.toUserControl3 = "GOINTHEBOX!";
}
else
{
u1.toUserControl3 = string.Empty;
}
}
On UserControl1
public string toUserControl3
{
get
{
return textBox1.Text;
}
set
{
textBox1.Text = value;
}
}
On UserControl1
public void textBox1_TextChanged(object sender, EventArgs e)
{
}
Changing the Text property on a control through a piece of code doesn't necessarily mean the value control will update. Typically you need some sort of binding between your property, in this case toUserControl3, and your control. You need a way to tell your control that value changed so it knows to update.
You could accomplish databinding in the following way:
Create a new class to handle state and binding: This eliminated any need to pass controls into constructors of other controls.
public class ViewModel : INotifyPropertyChanged
{
public string TextBoxText => CheckBoxChecked ? "GOINTOTHEBOX!" : string.Empty;
public bool CheckBoxChecked
{
get { return _checkBoxChecked; }
set
{
_checkBoxChecked = value;
OnPropertyChanged("CheckBoxChecked");
}
}
private bool _checkBoxChecked;
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string propertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
This is your main form
public void Form1
{
public Form1(ViewModel viewModel)
{
UserControl1.DataBindings.Add("TextBoxTextProperty", viewModel, "TextBoxText");
UserControl3.DataBindings.Add("MaterialCheckBoxCheckedProperty", viewModel, "CheckBoxChecked");
}
}
UserControl1
public void UserControl1()
{
public string TextBoxTextProperty
{
get { return textBox1.Text; }
set { textBox1.Text = value; }
}
}
UserControl3
public void UserControl3()
{
public bool MaterialCheckBoxCheckedProperty
{
get { return materialCheckBox1.Checked; }
set { materialCheckBox1.Checked = value; }
}
}
So, I'm doing a school project atm. The application needs to be able to calculate the area of squares, circles etc.
I have one form for each figure to calculate the area off. Right now I have a "main menu" and three forms (one for each figure) and I want to be able to assign a variable LatestResult within one form and access it from the main menu form.
Note: I want to be able to do this without "loading the variable into the new form" like this: Form1 FormMainMenu = new Form1(LatestResult)
I've been trying to work with Get & Set in my Variables.cs class, but I can't seem to make it work right, if it's possible to do it with this.
EDIT: Put my code in the post
My Variables.cslooks like this:
public static string latestresult2
{
get
{
return latestresult2;
}
set
{
latestresult2 = value;
}
}
And then I assign the value upon a button click in one form:
Variables.latestresult2 = breddeR + " * " + længdeR + " = " + resultat;
breddeR and længdeR are the int variables for my calculation and resultat is the result.
At last I try to do this in another form:
label1.Text = Variables.latestresult2;
EDIT 2:
From my MainView form
private void button1_Click(object sender, EventArgs e)
{
Form2 FormTrekant = new Form2();
FormTrekant.Show();
}
You may use the INotifyPropertyChanged interface to facilitate this. This interface works with both WinForms and WPF.
public class Form2 : Form, INotifyPropertyChanged
{
public string latestresult2;
public event PropertyChangedEventHandler PropertyChanged;
public string LatestResult2
{
get
{
return latestresult2;
}
set
{
latestresult2 = value;
this.OnPropertyChanged("LatestResult2");
}
}
private void OnPropertyChanged(string propertyName)
{
var handler = this.PropertyChanged;
if (handler == null)
{
return;
}
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
public class Form3 : Form, INotifyPropertyChanged
{
public string latestResult3;
public event PropertyChangedEventHandler PropertyChanged;
public string LatestResult3
{
get
{
return latestresult3;
}
set
{
latestresult3 = value;
this.OnPropertyChanged("LatestResult3");
}
}
private void OnPropertyChanged(string propertyName)
{
var handler = this.PropertyChanged;
if (handler == null)
{
return;
}
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
The INotifyPropertyChanged interface allows you to subscribe to another objects property changes. In the above code, the second form will raise the event when ever its LatestResult2 property has had its value changed.
Now you just have your Form1 subscribe to it.
public class MainForm : Form
{
private Form2 secondaryForm;
private Form3 thirdForm;
public string LatestValue {get; set;}
public void Form2ButtonClick() // Assume this happens in a button click event on MainWindow.
{
this.secondaryForm = new Form2();
this.secondaryForm.PropertyChanged += this.LatestValueChanged;
this.secondaryForm.Closing += this.ChildWindowClosing;
}
public void Form3ButtonClick() // Assume this happens in a button click event on MainWindow.
{
this.thirdForm = new Form3();
this.thirdForm.PropertyChanged += this.LatestValueChanged;
this.thirdForm.Closing += this.ChildWindowClosing;
}
private void LatestValueChanged(object sender, PropertyChangedEventArgs e)
{
// Do your update here.
if (sender == this.secondaryForm)
{
this.LatestValue = this.secondaryForm.LatestValue2;
}
else if (sender == this.thirdForm)
{
this.LatestValue = this.thirdForm.LatestValue3;
}
}
// Clean up our event handlers when either of the children forms close.
private void ChildWindowClosing(object sender, ClosingWindowEventHandlerArgs args)
{
if (sender == this.secondaryForm)
{
this.secondaryForm.Closing -= this.ChildWindowClosing;
this.secondaryForm.PropertyChanged -= this.LatestValueChanged;
}
else if (sender == this.thirdForm)
{
this.thirdForm.Closing -= this.ChildWindowClosing;
this.thirdForm.PropertyChanged -= this.LatestValueChanged;
}
}
}
Your MainWindow can now react to changes within Form2, without having to pass values around. One thing to note, is that you will want to unsubscribe from the event when the Form2 is closed. Otherwise you will leak.
You can specify the instance of the main view to the other view. This way, you can access the properties of the main view.
Some code to explaain;
public class MainView
{
public string LatestResult { get; set; }
}
public class ChildView
{
private readonly MainView MainView;
public ChildView(MainView mainView)
{
this.MainView = mainView;
}
public void Calculation()
{
//Some calculation
this.MainView.LatestResult = "Some result";
}
}
Now this code can be used like this:
var mainView = new MainView();
var childView = new ChildView(mainView);
childView.Calculation();
//mainView.LatestResult == "Some result"