Home > OS >  PropertyChanged called but UI doesn't update in MAUI
PropertyChanged called but UI doesn't update in MAUI

Time:10-28

Hi I'm developing simple test app in MAUI for windows.

The problem is that label in view page doesn't change when PropertyChanged called.

And I found that when i change [PortErrorMsg] Property, i can see PropertyChanged func called but

'get' in [PortErrorMsg] Property is not called.

(Debug.WriteLine("Message for checking whether get is working properly"); <-- this is not called.)

Here is my code below

Xaml :

<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             xmlns:vm="clr-namespace:NxJig.ViewModels"
             x:DataType="vm:JigViewModel"
             x:Class="NxJig.Views.JigPage"
             Title="JigPage">
    
    <ContentPage.BindingContext>
        <vm:JigViewModel/>
    </ContentPage.BindingContext>

    <VerticalStackLayout>
        <HorizontalStackLayout Margin="30">
            <Picker Title="Select Port" x:Name="picker1" SelectedIndexChanged="picker1_SelectedIndexChanged" FontSize="Large">
                <Picker.Items>
                    <x:String>COM1</x:String>
                    <x:String>COM2</x:String>
                    <x:String>COM3</x:String>
                    <x:String>COM4</x:String>
                    <x:String>COM5</x:String>
                    <x:String>COM6</x:String>
                    <x:String>COM7</x:String>
                    <x:String>COM8</x:String>
                    <x:String>COM9</x:String>
                    <x:String>COM10</x:String>
                    <x:String>COM11</x:String>
                    <x:String>COM12</x:String>
                    <x:String>COM13</x:String>
                    <x:String>COM14</x:String>
                    <x:String>COM15</x:String>
                    <x:String>COM16</x:String>
                    <x:String>COM17</x:String>
                    <x:String>COM18</x:String>
                    <x:String>COM19</x:String>
                    <x:String>COM20</x:String>
                </Picker.Items>
            </Picker>
        </HorizontalStackLayout>
            <Label Text="{Binding PortErrorMsg, Mode=TwoWay}" IsEnabled="True" FontSize="Large"/>
    </VerticalStackLayout>
</ContentPage>

Xaml behind code :

public partial class MainPage : ContentPage
{
    public MainPage()
    {
        InitializeComponent();
    }

    private void picker1_SelectedIndexChanged(object sender, EventArgs e)
    {
        ViewModel vm = new ViewModel();
        vm.SerialPortName = picker1.SelectedItem as string;
    }
}

ViewModel :

namespace NxJig.ViewModels
{
    public class JigViewModel : INotifyPropertyChanged
    {
        private SerialPort serialPort;
        private string serialPortName = "";
        private string defaultStr = "init text";
        private string successStr = "complete";
        private string failStr = "check the port again";
        public string portErrorMsg = defaultStr;

        public event PropertyChangedEventHandler PropertyChanged;

        public JigViewModel()
        {
            serialPort = new SerialPort();
            serialPort.BaudRate = 9600;
            serialPort.Parity = Parity.None;
            serialPort.StopBits = StopBits.One;
            serialPort.DataBits = 8;
        }

        public string SerialPortName
        {
            get => serialPortName;
            set
            {
                if (serialPortName != value)
                {
                    serialPortName = value;
                    Debug.WriteLine($"{serialPortName} is selected.");
                    OnPropertyChanged(nameof(SerialPortName));

                    serialPort.PortName = value;

                    if (!serialPort.IsOpen)
                    {
                        try
                        {
                            serialPort.Open();
                        
                            if(serialPort.IsOpen)
                            {
                                this.PortErrorMsg = successStr;
                            }
                        }
                        catch
                        {
                            this.PortErrorMsg = failStr;
                        }
                    }
                }
            }
        }

        public string PortErrorMsg
        {
            set
            {
                if (portErrorMsg != value)
                {
                    portErrorMsg = value;
                    Debug.WriteLine($"{portErrorMsg} is selected.");
                    OnPropertyChanged(nameof(PortErrorMsg));
                }
            }
            get
            {
                Debug.WriteLine("Message for checking whether get is working properly");
                return portErrorMsg;
            }
        }

        public void OnPropertyChanged(string propertyName)
        {
            if (PropertyChanged != null)
            {
                Debug.WriteLine($"propertyName is {propertyName}");
                PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
            }
            else
            {
                Debug.WriteLine("PropertyChanged is null !");
            }
        }
    }
}

I expected the label in xaml is changed when PropertyChanged called.

CodePudding user response:

From my point of view, two ViewModels are created. One is created in your xaml:

<ContentPage.BindingContext>
    <vm:JigViewModel/>
</ContentPage.BindingContext>

Another is created in your event:

private void picker1_SelectedIndexChanged(object sender, EventArgs e)
{
    ViewModel vm = new ViewModel(); // i supposed it's JigViewModel, right?
    vm.SerialPortName = picker1.SelectedItem as string;
}

Your view in your xaml is binded to the first one. When you trigger the picker1_SelectedIndexChanged, it just create a new ViewModel which has nothing related with your view. As a result, the PropertyChanged property has always been null and your label couldn't be changed.

Here i give you some suggestions. In your code-bind .CS

public partial class MainPage : ContentPage
{

    JigViewModel vm;

    public MainPage()
    {
        InitializeComponent();
        vm = new JigViewModel();
        this.BindingContext = vm;
    }

    private void picker1_SelectedIndexChanged(object sender, EventArgs e)
    {
        vm.SerialPortName = picker1.SelectedItem as string;
    }

And in your xaml, Delete the following code:

<ContentPage.BindingContext>
    <vm:JigViewModel/>
</ContentPage.BindingContext>

I hope my answer could help you.

CodePudding user response:

If I was on your place I would:

  1. Use CommunityToolkit.MVVM.
  2. Bind the Picker's SelectedItem to ObservableProperty with annotation.
  3. Write some code in partial void OnSerialPortChanged(...) if you need to.

You don't need those events. You don't need to manually call methods. You don't have to write a single line of boilerplate code.

  • Related