I am a beginner at Xamarin and I am trying to pass value from one page to another using QueryProperty, but I keep getting null values.
Here is the Page where the value comes from:
<StackLayout>
<Button Text="Pass" Command="{Binding passCommand}"></Button>
</StackLayout>
The code behind:
public Page()
{
InitializeComponent();
passCommand = new Command(passFunc);
BindingContext = this;
}
public ICommand passCommand { get; }
private async void passFunc()
{
string str = "Hello";
await Shell.Current.GoToAsync($"{nameof(Page3)}?str={str}");
}
And here is the receiving page:
<StackLayout>
<Label Text="{Binding str}"/>
</StackLayout>
The code behind:
[QueryProperty(nameof(str), nameof(str))]
public partial class Page3 : ContentPage
{
public Page3()
{
InitializeComponent();
BindingContext = this;
showdisp();
}
public string str { set; get; }
public async void showdisp()
{
await App.Current.MainPage.DisplayAlert("Hello", str, "OK");
}
}
The passed value should be put in the Label and the popup display alert. When I tried to put breakpoints, str value is still null. Navigating between pages are fine.
Can someone point out if where the error is T_T
Thanks in advance.
Your property "str" needs to raise a PropertyChanged event, so that the binding updates the value:
[QueryProperty(nameof(str), nameof(str))]
public partial class Page3 : ContentPage
{
public Page3()
{
InitializeComponent();
BindingContext = this;
// attention: this won't show the passed value,
// because QueryProperty values only are set after construction
//showdisp();
}
private string _str;
public string str
{
get => _str;
set
{
if(_str == value) return;
_str = value;
// Let the bound views know that something changed, so that they get updated
OnPropertyChanged();
// optional, call showdisp() when value changed
showdisp();
}
}
public async void showdisp()
{
await App.Current.MainPage.DisplayAlert("Hello", str, "OK");
}
}
However, since the parameter only is set after construction of Page3 finished, your showdisp() method won't have the correct value. You need to call it later.
You should also consider using a ViewModel and apply MVVM.
Related
I am currently doing a project with the MVVM method in NET MAUI to add, modify and delete drivers.
I have a template that contains the name, first name and number of points of the driver.
Then I have two views each with a model view:
- One that represents the list of my drivers with the possibility to add a driver, to select a driver from the list by going to another page (PageListPilotViewModel).
- And another one which represents the selected driver in another page to be able to modify its data and the possibility of removing it. (ProfilePilotViewModel)
At the moment I can select, add the driver and modify the driver in the other page. But I can't delete the driver in the profile page.
Here is what I have done so far:
-> Models : Pilote Model
public class PiloteModel : INotifyPropertyChanged
{
private string _nom;
public string Nom
{
get { return _nom; }
set { _nom = value; OnPropertyChanged(); }
}
private string _prenom;
public string Prenom
{
get { return _prenom; }
set { _prenom = value; OnPropertyChanged(); }
}
private int _points;
public int Points
{
get { return _points; }
set { _points = value; OnPropertyChanged(); }
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
-> View : ProfilPilotePage
<vm:PageListPiloteViewModel></vm:PageListPiloteViewModel>
</ContentPage.BindingContext>
<VerticalStackLayout>
<StackLayout>
<Entry Text="{Binding Pilote.Nom, Mode=TwoWay}" Placeholder="{Binding Nom}"></Entry>
<Entry Text="{Binding Pilote.Prenom}" Placeholder="{Binding Pilote.Prenom}"></Entry>
<Entry Text="{Binding Pilote.Points}" Placeholder="{Binding Pilote.Points}"></Entry>
<Button Command="{Binding OnsupprimerPiloteCommand}">
</Button>
</StackLayout>
-> code behind the profilePilotPage view
public partial class ProfilPilotePage : ContentPage
{
private PageListPiloteViewModel _viewModel;
public ProfilPilotePage(PageListPiloteViewModel viewModel)
{
InitializeComponent();
_viewModel = viewModel;
_viewModel.SupprimerPiloteClicked += OnSupprimerPiloteClicked;
BindingContext = _viewModel;
}
private void OnSupprimerPiloteClicked(object sender, PiloteModel e)
{
_viewModel.ListePilotes.Remove(e);
}
-> model views : PageListPilotViewModel , to be able to delete also the driver in the list
public ICommand OnsupprimerPiloteCommand { get; set; }
public PageListPiloteViewModel()
{
ValiderCommand = new Command(AjouterPilote);
OnsupprimerPiloteCommand = new Command(OnSupprimerPiloteClicked);
SelectedPilote = new PiloteModel();
ListePilotes = new ObservableCollection<Models.PiloteModel>();
ListePilotes.Add(new Models.PiloteModel { Nom = "Fabio", Prenom = "Quartaro", Points = 215 });
}
private void OnSupprimerPiloteClicked()
{
SupprimerPiloteClicked?.Invoke(this, SelectedPilote);
}
->code behind the PageListPiloteView: with the error I encounter on the last :
await Navigation.PushAsync(new ProfilePilotPage{ BindingContext = viewModel }) :
CS7036 Error None of the specified arguments match the 'viewModel' mandatory parameter of 'ProfilePilotPage.ProfilePilotPage(PageListPilotViewModel)'
private async void SelectionnerPilote(object sender, SelectionChangedEventArgs e)
{
PiloteModel selectedPilote = (PiloteModel)((CollectionView)sender).SelectedItem;
ProfilPiloteViewModel viewModel = new ProfilPiloteViewModel();
viewModel.Pilote = selectedPilote;
await Navigation.PushAsync(new ProfilPilotePage{ BindingContext = viewModel });
}
}
Do you have any idea how to make the specified arguments mandatory please ?
You've mixed up constructor and initializer.
This line
await Navigation.PushAsync(new ProfilPilotePage{ BindingContext = viewModel });
should be
await Navigation.PushAsync(new ProfilPilotePage(viewModel));
The reason for this is that you're defining an argument in the signature of the ProfilPilotePage's constructor:
public ProfilPilotePage(PageListPiloteViewModel viewModel)
{
//...
}
Therefore, you must pass the ViewModel argument.
At first, you can try to use the ewerspej's solution or only add a default construction method without any parameter into the ProfilPilotePage to fix the error caused by await Navigation.PushAsync(new ProfilePilotPage{ BindingContext = viewModel }) . Such as:
public partial class ProfilPilotePage : ContentPage
{
private PageListPiloteViewModel _viewModel;
public ProfilPilotePage()
{
InitializeComponent();
}
public ProfilPilotePage(PageListPiloteViewModel viewModel)
{
InitializeComponent();
_viewModel = viewModel;
_viewModel.SupprimerPiloteClicked += OnSupprimerPiloteClicked;
BindingContext = _viewModel;
}
}
And then I saw you used both the mvvm and the code behind. You can remove the OnSupprimerPiloteClicked(object sender, PiloteModel e) in the page.cs and change the OnSupprimerPiloteClicked() in the view model. Such as:
private void OnSupprimerPiloteClicked()
{
ListePilotes.Remove(SelectedPilote);
}
Finally, I saw SelectedPilote = new PiloteModel(); in your viewmodel. Which item in the list did you want to delete? I think it should be the seleted item not a new PiloteModel().
In my application I have a situation where I want to display some object on page and then change this object for different one.
So, let's consider I have MainPage.xaml.cs like this:
...
public Foo Item { get; set; }
public bool SomeCheck {
get {
return Item.Bar != "";
}
}
public MainPage() {
InitializeComponent();
SetItem();
BindingContext = this;
}
private void SetItem() {
Item = DifferentClass.GetNewItem();
}
private void Next_Clicked(object sender, EventArds e){
SetItem();
}
...
and MainPage.xaml like this:
...
<Label Text="{Binding Item.Bar}" IsVisible="{Binding SomeCheck}" />
<Button Text="Next" Clicked="Next_Clicked" />
...
So I want to bind whole page to BindingContext, to achieve this I've set BindingContext = this;. Behaviour which I want is to show Bar property of different objects returned by GetNewItem() and what I get is frozen page. In debugger Item is changing, but on page I have always value which I've set at the first call.
So the question is: can I somehow update BindingContext to show what I want? I tried calling OnPropertyChanged() but it doesn't work for me.
I know I can set up whole object like
BindingContext = { Bar = Item.Bar, SomeCheck = Item.Bar != "" };
and the it works, but of course my real scenario is more complex so I don't want to go this way.
Use OnPropertyChanged:
XAML:
<Label Text="IsVisible" IsVisible="{Binding MyIsVisible}" />
In the viewmodel, in your case in MainPage.xaml.cs:
private bool myIsVisible = true;
public bool MyIsVisible
{
get => myIsVisible;
set
{
myIsVisible = value;
OnPropertyChanged(nameof(MyIsVisible));
}
}
I am trying to get the content of a TextBox updated using Binding in a MVVM environment. When a Button receive focus, it passes a value, and that value should be reflected in the TextBox. I seem to have the first part right, however seems to be struggling at passing the value..
I know the question about MVVM has been asked before (including by myself), but I really cannot get it, for some reasons..
So I start with my model:
public class iText : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private string _text;
public string Text
{
get { return _text; }
set
{
_text = value;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(Text)));
}
}
I then continue with my ViewModel:
private iText _helper = new iText();
public iText Helper
{
get { return _helper; }
set
{
_helper = value;
}
}
The XAML page:
<Page.Resources>
<scan:ModelDataContext x:Key="ModelDataContext" x:Name="ModelDataContext"/>
</Page.Resources>
<TextBox Text="{Binding Helper.Text, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"/>
I then try to update the Text from MainPage.cs
public sealed partial class MainPage : Page
{
public MainPageViewModel iText { get; set; }
public MainPage()
{
InitializeComponent();
iText = new MainPageViewModel();
}
private void btn_GotFocus(object sender, RoutedEventArgs e)
{
var str = "test"
iText.Helper.Text = str;
}
I could really appreciate if someone could tell me what I do wrong, and where. Thanks so much in advance.
In your MainPage constructor, try setting the datacontext to your ViewModel.
Something like...
public MainPage()
{
InitializeComponent();
iText = new MainPageViewModel();
this.dataContext = iText;
}
I have spent a couple hours trying to figure out this ONE problem. Here's what is happening:
I am trying to bind a Title to my XAML file from my ViewModel. All of the code executes (I checked using breakpoints/watch), but the binding doesn't actually work. I am very new to development and especially MVVM, so I am having a hard time figuring this out. Relevant code:
App.Xaml.Cs
private static MainPageViewModel _mainPageViewModel = null;
public static MainPageViewModel MainPageViewModel
{
get
{
if (_mainPageViewModel == null)
{
_mainPageViewModel = new MainPageViewModel();
}
return _mainPageViewModel;
}
}
MainPageModel
public class MainPageModel : BaseModel
{
private string _pageTitle;
public string PageTitle
{
get { return _pageTitle; }
set
{
if (_pageTitle != value)
{
NotifyPropertyChanging();
_pageTitle = value;
NotifyPropertyChanged();
}
}
}
MainPageViewModel
private void LoadAll()
{
var page = new MainPageModel();
page.PageTitle = "title";
MainPageViewModel
public MainPageViewModel()
{
LoadAll();
}
MainPage.Xaml.Cs
public MainPage()
{
InitializeComponent();
DataContext = App.MainPageViewModel;
}
MainPage.Xaml
<Grid x:Name="LayoutRoot">
<phone:Panorama Title="{Binding PageTitle}">
Do I need a using statement in the Xaml too? I thought I just needed to set the data context in the MainPage.Xaml.Cs file.
I'm pretty sure I've posted all of the relevant code for this. Thanks everyone!!
The problem is here, in the view model class:
private void LoadAll()
{
var page = new MainPageModel();
page.PageTitle = "title";
All you've done here is create a local object "page" -- this will not be accessible anywhere outside the local scope. I suppose what you meant to do is make "page" a member of "MainPageViewModel":
public class MainPageViewModel
{
public MainPageModel Model { get; private set; }
private void LoadAll()
{
_page = new MainPageModel();
_page.PageTitle = "title";
}
}
This way, you'll be able to bind to the "PageTitle" property -- but remember, it's a nested property, so you'll need:
<phone:Panorama Title="{Binding Model.PageTitle}">
I want to pass a value from MainWindow into my UserControl! I passed a value to my UserControl and the UserControl showed me the value in a MessageBox, but it is not showing the value in a TextBox. Here is my code:
MainWindow(Passing Value To UserControl)
try
{
GroupsItems abc = null;
if (abc == null)
{
abc = new GroupsItems();
abc.MyParent = this;
abc.passedv(e.ToString(), this);
}
}
catch (Exception ee)
{
MessageBox.Show(ee.Message);
}
UserControl
public partial class GroupsItems : UserControl
{
public MainWindow MyParent { get; set; }
string idd = "";
public GroupsItems()
{
InitializeComponent();
data();
}
public void passedv(string id, MainWindow mp)
{
idd = id.ToString();
MessageBox.Show(idd);
data();
}
public void data()
{
if (idd!="")
{
MessageBox.Show(idd);
texbox.Text = idd;
}
}
}
EDIT(using BINDING and INotifyProperty )
.....
public GroupsItems()
{
InitializeComponent();
}
public void passedv()
{
textbox1.Text = Text;
}
}
public class Groupitm : INotifyPropertyChanged
{
private string _text = "";
public string Text
{
get { return _text; }
set
{
if (value != _text)
{
_text = value;
NotifyPropertyChanged();
}
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected void NotifyPropertyChanged(String propertyName = "")
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
Problem here is with reference.
When you create new object in code behind, new object will be created and this is not the same object which you have in xaml code. So you should use following code:
<local:GroupsItems x:Name="myGroupsItems"/>
and in code behind you don't have to create new object. You should use object that you added in XAML:
...
myGroupsItems.MyParent = this;
myGroupsItems.passedv(e.ToString(), this);
...
Here is example solution (sampleproject).
You are calling data in the constructor when idd is still "" which results in the text box still being empty. Changing the MyParent property does not change that. Only passedv does. But at that point you do not have the parent set. Just call data in passedv, too.
Try this:
public partial class GroupsItems : UserControl
{
//properties and methods
private string idd="";
public string IDD
{
get{return idd;}
set{
idd=value;
textBox1.Text=idd;
}
}
//other properties and methods
}
Usage:
In your Main form:
abc = new GroupsItems();
abc.IDD="sometext";
MainGrid1.Children.Add(abc); //Grid or any other container for your UserControl
In your Binding example, your GroupItem class looks ok, except that you need to pass in the name of the changed property:
public string Text
{
get { return _text; }
set
{
if (value != _text)
{
_text = value;
NotifyPropertyChanged("Text");
}
}
}
Now, in GroupsItems, you shouldn't be accessing the TextBox. In WPF, we manipulate the data, not the UI... but as we use Binding objects to data bind the data to the UI controls, they automatically update (if we correctly implement the INotifyPropertyChanged interface).
So first, let's add a data property into your code behind (which should also implement the INotifyPropertyChanged interface) like you did in your GroupItem class:
private GroupItem _item = new GroupItem();
public GroupItem Item
{
get { return _item; }
set
{
if (value != _item)
{
_item = value;
NotifyPropertyChanged("Item");
}
}
}
Now let's try using a Binding on a TextBox.Text property:
<TextBox Text="{Binding Item.Text}" />
See how we bind the Text property of your GroupItem class to the TextBox.Text property... now all we need to do is to change the value of the Item.Text property and watch it update in the UI:
<Button Content="Click me" Click="Button_Click" />
...
private void Button_Click(object sender, RoutedEventArgs e)
{
Item.Text = "Can you see me now?";
}
Alternatively, you could put this code into your passedv method if you are calling that elsewhere in your project. Let me know how you get on.
UPDATE >>>
In your GroupItem class, try changing the initialization to this:
private string _text = "Any text value";
Can you now see that text in the UI when you run the application? If not, then try adding/copying the whole Text property into your code behind and changing the TextBox declaration to this:
<TextBox Text="{Binding Text}" />
If you can't see the text value now, you've really got problems... you have implemented the INotifyPropertyChanged interface in your code behind haven't you?