Returning a Selection from a ListView through Navigation.PopModalAsync

Grime 786 Reputation points
2021-04-30T02:53:39.7+00:00

My start page (OperatorsPage.xaml) has an Entry where the Text has a binding to OperatorName, which is not initially set, so the Placeholder is set to "No Currently Selected Operator". From this page, you can click the "Select Operator" button, and a Navigation.PushModalAsync(new SelectOperatorPage()); is opened.
In the SelectOperatorPage, a ListView of available "operators" is created from my ViewModel/OperatorViewModel.cs.
From this ListView, there is an ItemSelected="OperatorListView_ItemSelected" from which a Navigation.PopModalAsync(); is performed to get us back to the OperatorsPage.xaml.

My problem is then alerting the Entry that there is now {Binding OperatorName} text to display, and not the Placeholder.

Here is the XAML and code-behinds for the two pages...
OperatorsPage.xaml

<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             x:Class="HandsFreeNotes.View.OperatorsPage">
    <ContentPage.Content>
        <Grid BackgroundColor="White">
            <Grid.RowDefinitions>
                <RowDefinition Height="40"/>
                <RowDefinition Height="40"/>
                <RowDefinition Height="40"/>
                <RowDefinition Height="70"/>
                <RowDefinition Height="*"/>
                <RowDefinition Height="50"/>
            </Grid.RowDefinitions>

            <Grid.ColumnDefinitions>
                <ColumnDefinition Width="50"/>
                <ColumnDefinition Width="*"/>
                <ColumnDefinition Width="50"/>
            </Grid.ColumnDefinitions>

            <Button
                x:Name="BackButton"
                Text="Back"
                Grid.Row="0"
                Grid.Column="0"
                Grid.ColumnSpan="3"
                TextColor="Black"
                FontAttributes="Bold"
                HeightRequest="40"
                VerticalOptions="Center"
                HorizontalOptions="End"
                Margin="0,0,20,0"
                Clicked="BackButton_Clicked"/>

            <Image 
                Source="hfn256"
                Margin="2"
                Grid.Row="1" 
                Grid.Column="0"
                Grid.ColumnSpan="3"
                HorizontalOptions="Center"
                BackgroundColor="Transparent"/>

            <Label 
                Text="Operator"
                FontSize="Large"
                TextColor="Black"
                FontAttributes="Bold"
                Margin =" 0, 0, 0, 0"
                Grid.Row="2" 
                Grid.Column="0"
                Grid.ColumnSpan="3"
                HorizontalOptions="Center"
                VerticalOptions="End"
                BackgroundColor="Transparent"/>

            <Entry 
                x:Name="SelectedOperatorEntry"
                Text="{Binding OperatorName}"
                TextColor="DarkGreen"
                IsReadOnly="True"
                Placeholder="No Currently Selected Operator"
                PlaceholderColor="LightGray"
                FontSize="Medium"
                FontAttributes="Bold"
                Margin ="0, 0, 0, 0"
                Grid.Row="3" 
                Grid.Column="0"
                Grid.ColumnSpan="3"
                HorizontalOptions="Center"
                VerticalOptions="Center"
                BackgroundColor="Transparent"/>

            <ScrollView 
                Orientation="Vertical"
                Grid.Row="4" 
                Grid.Column="0"
                Grid.ColumnSpan="3">
                <Grid BackgroundColor="White" >
                    <Grid.RowDefinitions>
                        <RowDefinition Height="50"/>
                        <RowDefinition Height="50"/>
                        <RowDefinition Height="50"/>
                        <RowDefinition Height="100"/>
                        <RowDefinition Height="20"/>
                    </Grid.RowDefinitions>

                    <Grid.ColumnDefinitions>
                        <ColumnDefinition Width="50"/>
                        <ColumnDefinition Width="*"/>
                        <ColumnDefinition Width="50"/>
                    </Grid.ColumnDefinitions>

                    <Button
                        x:Name="SelectOperatorButton"
                        Text="Select Operator"
                        FontSize="Medium"
                        Grid.Row="0"
                        Grid.Column="1"
                        TextColor="Black"
                        FontAttributes="Bold"
                        BackgroundColor="White"
                        Margin="0,0,0,0"
                        HorizontalOptions="Center"
                        VerticalOptions="Start"
                        Clicked="SelectOperatorButton_Clicked"/>

                    <Button
                        x:Name="EditOperatorButton"
                        Text="Edit Selected Operator"
                        FontSize="Medium"
                        Grid.Row="1"
                        Grid.Column="1"
                        TextColor="Black"
                        FontAttributes="Bold"
                        BackgroundColor="White"
                        Margin="0,0,0,0"
                        HorizontalOptions="Center"
                        VerticalOptions="Start"/>

                    <Button
                        x:Name="DeleteOperatorButton"
                        Text="Delete Selected Operator"
                        FontSize="Medium"
                        Grid.Row="2"
                        Grid.Column="1"
                        TextColor="Black"
                        FontAttributes="Bold"
                        BackgroundColor="White"
                        Margin="0,0,0,0"
                        HorizontalOptions="Center"
                        VerticalOptions="Start"/>

                    <Button
                        x:Name="AddOperatorButton"
                        Text="Add New Operator"
                        FontSize="Medium"
                        Grid.Row="3"
                        Grid.Column="1"
                        HorizontalOptions="Center"
                        VerticalOptions="End"
                        TextColor="Black"
                        FontAttributes="Bold"
                        BackgroundColor="White"
                        Margin="0,0,0,0"
                        Clicked="AddOperatorButton_Clicked"/>

                </Grid>

            </ScrollView>

        </Grid>
    </ContentPage.Content>
</ContentPage>

OperatorsPage.xaml.cs:

using HandsFreeNotes.ViewModel;
using HandsFreeNotes.Model;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

using Xamarin.Forms;
using Xamarin.Forms.Xaml;

namespace HandsFreeNotes.View
{
    [XamlCompilation(XamlCompilationOptions.Compile)]
    public partial class OperatorsPage : ContentPage
    {
        OperatorViewModel ovm;
        OperatorModel opm;

        public OperatorsPage(OperatorViewModel _ovm)
        {
            // add a bit of padding to cater to the "notch" on the iPhone.
            if (Device.RuntimePlatform == Device.iOS)
            {
                Padding = new Thickness(0, 40, 0, 0);
            }

            // DisplayAlert("Alert"+" 1:", "Message: " + "tttt", "Exit");

            InitializeComponent();

            ovm = _ovm;
            opm = ovm.SelectedOperator;
            // string name = opm.OperatorName;

            BindingContext = opm;
            //if (name != null) 
            //{ 
            //    DisplayAlert("Alert"+" 2:", "Message: "+ "not null", "Exit");
            //} 
            //else
            //{
            //    DisplayAlert("Alert" + " 2:", "Message: " + "null", "Exit");
            //}
        }

        private async void BackButton_Clicked(object sender, EventArgs e)
        {
            await Navigation.PopModalAsync();
        }

        private async void AddOperatorButton_Clicked(object sender, EventArgs e)
        {
            await Navigation.PushModalAsync(new NewOperatorPage());
        }

        private async void SelectOperatorButton_Clicked(object sender, EventArgs e)
        {
            await Navigation.PushModalAsync(new SelectOperatorPage());
        }
    }
}

SelectOperator'sPage.xaml:

<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             x:Class="HandsFreeNotes.View.SelectOperatorPage">

    <ContentPage.Content>
        <Grid BackgroundColor="White">
            <Grid.RowDefinitions>
                <RowDefinition Height="40"/>
                <RowDefinition Height="40"/>
                <RowDefinition Height="80"/>
                <RowDefinition Height="*"/>
                <RowDefinition Height="50"/>
            </Grid.RowDefinitions>

            <Grid.ColumnDefinitions>
                <ColumnDefinition Width="50"/>
                <ColumnDefinition Width="*"/>
                <ColumnDefinition Width="50"/>
            </Grid.ColumnDefinitions>

            <Button
                x:Name="BackButton"
                Text="Back"
                Grid.Row="0"
                Grid.Column="0"
                Grid.ColumnSpan="3"
                TextColor="Black"
                FontAttributes="Bold"
                HeightRequest="40"
                VerticalOptions="CenterAndExpand"
                HorizontalOptions="End"
                Margin="0,0,20,0"
                Clicked="BackButton_Clicked"/>

            <Image 
                Source="hfn256"
                Margin="2"
                Grid.Row="1" 
                Grid.Column="0"
                Grid.ColumnSpan="3"
                HorizontalOptions="Center"
                BackgroundColor="Transparent"/>

            <Label 
                Text="Select Operator..."
                FontSize="Large"
                TextColor="Black"
                FontAttributes="Bold"
                Grid.Row="2" 
                Grid.Column="0"
                Grid.ColumnSpan="3"
                HorizontalOptions="Center"
                VerticalOptions="Start"
                Margin="20"
                BackgroundColor="Transparent"/>

            <ListView
                x:Name="OperatorListView"
                Grid.Row="3"
                Grid.Column="0"
                Grid.ColumnSpan="3"
                RowHeight="140"
                ItemSelected="OperatorListView_ItemSelected">
                <ListView.ItemTemplate>
                    <DataTemplate>
                        <ViewCell>
                            <ContentView>
                                <Grid>
                                    <Grid.RowDefinitions>
                                        <RowDefinition Height="*"/>
                                    </Grid.RowDefinitions>
                                    <Grid.ColumnDefinitions>
                                        <ColumnDefinition Width="100"/>
                                        <ColumnDefinition Width="100"/>
                                        <ColumnDefinition Width="200"/>
                                    </Grid.ColumnDefinitions>

                                    <Image
                                        Source="{Binding OperatorAvatar}"
                                        Grid.Row="0"
                                        Grid.Column="0"
                                        HeightRequest="200"
                                        WidthRequest="100"/>

                                    <Label
                                        Text="{Binding OperatorName}"
                                        FontAttributes="Bold"
                                        Grid.Row ="0"
                                        Grid.Column="1"/>

                                    <StackLayout
                                        Grid.Row="0"
                                        Grid.Column="2">

                                        <Label
                                            Text="{Binding OperatorEmail}"
                                            FontAttributes="Italic"
                                            FontSize="Micro"
                                            HeightRequest="30"
                                            HorizontalOptions="Start"/>

                                        <Label
                                            Text="{Binding OperatorPhone}"
                                            FontAttributes="Italic"
                                            FontSize="Small"
                                            HeightRequest="30"
                                            HorizontalOptions="Start"/>

                                    </StackLayout>
                                </Grid>
                            </ContentView>
                        </ViewCell>
                    </DataTemplate>
                </ListView.ItemTemplate>
            </ListView>

        </Grid>    </ContentPage.Content>
</ContentPage>

and SelectOperatorPage.xaml.cs:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

using Xamarin.Forms;
using Xamarin.Forms.Xaml;
using HandsFreeNotes.ViewModel;
using HandsFreeNotes.Model;

namespace HandsFreeNotes.View
{
    [XamlCompilation(XamlCompilationOptions.Compile)]
    public partial class SelectOperatorPage : ContentPage
    {
        OperatorViewModel ovm;

        public SelectOperatorPage()
        {
            // add a bit of padding to cater to the "notch" on the iPhone.
            if (Device.RuntimePlatform == Device.iOS)
            {
                Padding = new Thickness(0, 40, 0, 0);
            }

            InitializeComponent();

            ovm = new OperatorViewModel();
            // ovm.HFNName = "Brookey's Hands Free Notes";
            BindingContext = ovm;
        }

        protected override async void OnAppearing()
        {
            base.OnAppearing();
            await ovm.GetOperators();
            OperatorListView.ItemsSource = ovm.OperatorList;
        }

        private async void BackButton_Clicked(object sender, EventArgs e)
        {
            await Navigation.PopModalAsync();
        }

        private async void OperatorListView_ItemSelected(object sender, SelectedItemChangedEventArgs e)
        {
            if (e.SelectedItem != null)
            {
                // Typecast the e.SelectedItem object to an OperatorModel
                ovm.SelectedOperator = (OperatorModel)e.SelectedItem;
                await DisplayAlert("Alert", "Selected Operator: " + ovm.SelectedOperator.OperatorName, "Exit");
                await Navigation.PopModalAsync();
            }
        }
    }
}
Xamarin
Xamarin
A Microsoft open-source app platform for building Android and iOS apps with .NET and C#.
5,296 questions
C#
C#
An object-oriented and type-safe programming language that has its roots in the C family of languages and includes support for component-oriented programming.
10,288 questions
{count} votes

Accepted answer
  1. JessieZhang-MSFT 7,706 Reputation points Microsoft Vendor
    2021-04-30T06:30:37.173+00:00

    Hello,

    Welcome to our Microsoft Q&A platform!

    I found there are several problems in your app.

    1. According to your description,the first page should be OperatorsPage not SelectOperatorPage:
       MainPage = new OperatorsPage(); // not `MainPage = new SelectOperatorPage(); `  
      
    2. When you come back from page SelectOperatorPage, you can pass the selected OperatorModel model to page OperatorsPage by several methods(e.g. EventHandler , MessageCenter ).
    3. Here I will use EventHandler to pass data back.

    You can refer to the following code:

    OperatorsPage.xaml.cs

    When navigating to page SelectOperatorPage, we can init ReturnValue as follows

                page.ReturnValue += delegate (object s, OperatorModel operatorModel)  
                {  
                    BackCall(s, operatorModel);  
                };  
    

    The whole code is :

       public partial class OperatorsPage : ContentPage  
        {  
            OperatorViewModel ovm;  
            OperatorModel opm;  
            public OperatorsPage()  
            {  
                InitializeComponent();  
      
                //ovm = _ovm;  
                //opm = ovm.SelectedOperator;  
                // string name = opm.OperatorName;  
      
                opm = new OperatorModel();  
                BindingContext = opm;  
            }  
      
            private async void BackButton_Clicked(object sender, EventArgs e)  
            {  
               // await Navigation.PopModalAsync();  
            }  
      
            private async void AddOperatorButton_Clicked(object sender, EventArgs e)  
            {  
                //await Navigation.PushModalAsync(new NewOperatorPage());  
            }  
      
            private async void SelectOperatorButton_Clicked(object sender, EventArgs e)  
            {  
                SelectOperatorPage page = new SelectOperatorPage();  
      
                page.ReturnValue += delegate (object s, OperatorModel operatorModel)  
                {  
                    BackCall(s, operatorModel);  
                };  
      
                await Navigation.PushModalAsync(page);  
            }  
      
            private void BackCall(object s, OperatorModel model)  
            {  
                opm.OperatorName = model.OperatorName;  
            }  
        }  
    

    OperatorModel.cs

    To make the UI refresh automatically when the value of OperatorName is changed ,we can make OperatorModel implement interface INotifyPropertyChanged

    public class OperatorModel: INotifyPropertyChanged  
    {  
        public int OperatorID { get; set; }  
      
        string _operatorName;  
        public string OperatorName  
        {  
            set { SetProperty(ref _operatorName, value); }  
      
            get { return _operatorName; }  
        }  
      
        public string OperatorPhone { get; set; }  
        public string OperatorEmail { get; set; }  
        public string OperatorAvatar { get; set; }  
      
        public OperatorModel() {   
          
        }      
        public OperatorModel(int operatorID, string operatorName, string operatorPhone, string operatorEmail, string operatorAvatar)  
        {  
            OperatorID = operatorID;  
            OperatorName = operatorName;  
            OperatorPhone = operatorPhone;  
            OperatorEmail = operatorEmail;  
            OperatorAvatar = operatorAvatar;  
        }          
        bool SetProperty<T>(ref T storage, T value, [CallerMemberName] string propertyName = null)  
        {  
            if (Object.Equals(storage, value))  
                return false;  
      
            storage = value;  
            OnPropertyChanged(propertyName);  
            return true;  
        }      
        protected void OnPropertyChanged([CallerMemberName] string propertyName = null)  
        {  
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));  
        }  
      
        public event PropertyChangedEventHandler PropertyChanged;  
    }  
    

    SelectOperatorPage.xaml.cs

    In this page, we add EventHandler ReturnValue , and when we select one item, we can pass the selected OperatorModel back to page OperatorsPage .

       public partial class SelectOperatorPage : ContentPage  
        {  
            OperatorViewModel ovm;  
      
            public EventHandler<OperatorModel> ReturnValue;  
      
            public SelectOperatorPage()  
            {  
                InitializeComponent();  
      
                ovm = new OperatorViewModel();  
                // ovm.HFNName = "Brookey's Hands Free Notes";  
                BindingContext = ovm;  
            }  
      
            protected override async void OnAppearing()  
            {  
                base.OnAppearing();  
                await ovm.GetOperators();  
                OperatorListView.ItemsSource = ovm.OperatorList;  
            }  
      
            private async void BackButton_Clicked(object sender, EventArgs e)  
            {  
                await Navigation.PopModalAsync();  
            }  
      
            private async void OperatorListView_ItemSelected(object sender, SelectedItemChangedEventArgs e)  
            {  
                if (e.SelectedItem != null)  
                {  
                    // Typecast the e.SelectedItem object to an OperatorModel  
                    OperatorModel  operatorModel = (OperatorModel)e.SelectedItem;  
                    ovm.SelectedOperator = operatorModel;  
      
                    await DisplayAlert("Alert", "Selected Operator: " + ovm.SelectedOperator.OperatorName, "Exit");  
      
                    EventHandler<OperatorModel> handler = ReturnValue;  
                    if (handler != null)  
                    {  
                        handler(this, operatorModel);  
                    }  
      
                    await Navigation.PopModalAsync();  
                }  
            }  
        }  
    

    The result is:

    92816-image.png

    Best Regards,

    Jessie Zhang

    ---
    If the response is helpful, please click "Accept Answer" and upvote it.

    Note: Please follow the steps in our documentation to enable e-mail notifications if you want to receive the related email notification for this thread.

    0 comments No comments

2 additional answers

Sort by: Most helpful
  1. Grime 786 Reputation points
    2021-05-01T05:46:24.983+00:00

    Awesome job @JessieZhang-MSFT .
    It is working as it should thanks to you.
    If you have the time, I would love an explanation on some of your methods:
    In OperatorsPage.xaml.cs...

                SelectOperatorPage page = new SelectOperatorPage();  
                page.ReturnValue += delegate (object s, OperatorModel operatorModel)  
    

    In OperatorModel.cs...

            bool SetProperty<T>(ref T storage, T value, [CallerMemberName] string propertyName = null)  
            {  
                if (Object.Equals(storage, value))  
                    return false;  
      
                storage = value;  
                OnPropertyChanged(propertyName);  
                return true;  
            }  
            protected void OnPropertyChanged([CallerMemberName] string propertyName = null)  
            {  
                PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));  
            }  
      
            public event PropertyChangedEventHandler PropertyChanged;  
    

    Thanks again!

    0 comments No comments

  2. Grime 786 Reputation points
    2021-04-30T04:49:16.237+00:00

    Thanks for responding @JessieZhang-MSFT . The Reply option is not working for me. Let's try here...

    OperatorModel.cs:

    using System;  
    using System.Collections.Generic;  
    using System.Text;  
    using SQLite;  
      
    namespace HandsFreeNotes.Model  
    {  
        public class OperatorModel  
        {  
            public int OperatorID { get; set; }  
            public string OperatorName { get; set; }  
            public string OperatorPhone { get; set; }  
            public string OperatorEmail { get; set; }  
            public string OperatorAvatar { get; set; }  
      
            public OperatorModel(int operatorID, string operatorName, string operatorPhone, string operatorEmail, string operatorAvatar)  
            {  
                OperatorID = operatorID;  
                OperatorName = operatorName;  
                OperatorPhone = operatorPhone;  
                OperatorEmail = operatorEmail;  
                OperatorAvatar = operatorAvatar;  
            }  
        }  
      
      
    }  
    

    OperatorViewModel.cs:

    using HandsFreeNotes.Model;  
    using System;  
    using System.Collections.Generic;  
    using System.ComponentModel;  
    using System.Text;  
    using System.Threading.Tasks;  
    using Xamarin.Forms;  
      
    namespace HandsFreeNotes.ViewModel  
    {  
        public class OperatorViewModel : INotifyPropertyChanged  
        {  
            private string _HFNName;  
      
            public string HFNName  
            {  
                get  
                {  
                    return _HFNName;  
                }  
                  
                set  
                {  
                    _HFNName = value;  
                    RaisePropertyChanged("HFNName");  
                }  
            }  
      
            public List<OperatorModel> OperatorList;  
            public OperatorModel SelectedOperator;  
      
            public async Task<bool> GetOperators()  
            {  
                if (OperatorList == null)  
                {  
                    OperatorList = new List<OperatorModel>();  
                    OperatorList.Add(new OperatorModel(1, "Grime", "+61419659866", "apps@grime.net", "gs300.png"));  
                    OperatorList.Add(new OperatorModel(2, "Brookey", "+61419659865", "brookey@grime.net", "gmb.jpg"));  
                    OperatorList.Add(new OperatorModel(3, "CC", "+61422192455", "shaunachick@gmail.com", "cc.jpg"));  
                    OperatorList.Add(new OperatorModel(4, "Monte", "+61412595877", "monte@aussieweb.com.au", "monte2a.jpg"));  
                    OperatorList.Add(new OperatorModel(5, "Jeff", "+61409998196", "jeff@content2convert.com.au", "jeff.jpg"));  
                }  
      
                return true;  
            }  
      
            public event PropertyChangedEventHandler PropertyChanged;  
      
            protected void RaisePropertyChanged(string prop)  
            {  
                if (PropertyChanged != null) { PropertyChanged(this, new PropertyChangedEventArgs(prop)); }  
            }  
        }  
    }  
    
    
    
    
      
    
    0 comments No comments