简体   繁体   English

服务完成后属性未更新

[英]Property not updating after service completes

I'm trying to get my head around data binding in Xamarin.Forms . 我正在设法Xamarin.Forms数据绑定。 I've read lots of the guides and played with some examples and I am now trying to implement some of my own basic binding. 我已经阅读了许多指南并使用了一些示例,现在我正在尝试实现一些自己的基本绑定。

I've got a Strings file in which I've declared an empty variable: 我有一个Strings文件,其中声明了一个空变量:

public static class Strings
{
    public static string UserDisplayName;
}

On load of my View, it runs an async function to grab data from a Azure SQL DB which then populates the string 在加载View时,它运行一个异步函数以从Azure SQL数据库中获取数据,然后填充字符串

Strings.UserDisplayName = user.FirstName;

In my view page I've bound a label to a variable userDisplayNm 在我的视图页面中,我已将标签绑定到变量userDisplayNm

<Label Text="{Binding UserDisplayNm}"></Label>

In my ViewModel I have the following to set UserDisplayNm, however it only ever returns "Welcome, ". 在我的ViewModel中,可以通过以下方式设置UserDisplayNm,但是它只会返回“ Welcome”。 How do i get it to fire this again after the sync service has completed & the Strings.UserDisplayname value changes? 同步服务完成且Strings.UserDisplayname值更改后,如何才能再次触发它? I think I'm missing a link to an PropertyChanged event or something? 我想我缺少指向PropertyChanged事件的链接?

    namespace Travel_Comp.ViewModels
    {
        public sealed class MenuViewModel : INotifyPropertyChanged
        {
        public event PropertyChangedEventHandler PropertyChanged;

        public MenuViewModel()
        {
            this.UserDisplayNm = Strings.UserDisplayName;
        }

        public string UserDisplayNm
        {
            set
            {
                if (Strings.UserDisplayName != value)
                {
                    value = Strings.UserDisplayName;

                    if (PropertyChanged != null)
                    {
                        PropertyChanged(this, new PropertyChangedEventArgs("UserDisplayNm"));
                    }
                }
            }
            get
            {
                return "Welcome, " + Strings.UserDisplayName;
            }
        }

    }
}

EDIT: 编辑:

Thanks for your replies. 多谢您的回覆。 I think I'm getting closer based on the replies below, here is what I've now got, although The MenuViewModel.LoadAsync() is throwing an error "Inaccessible due to its protection level", so i can't compile to check it yet. 我想根据下面的答复我正在接近,这是我现在得到的,尽管MenuViewModel.LoadAsync()抛出错误“由于其保护级别而无法访问”,所以我无法编译进行检查还没完成。 Is this what you were suggesting & any ideas on the Protection level issue?? 这是您的建议以及有关保护级别问题的任何想法吗?

Strings file: 字符串文件:

public static class Strings
{
    public static string UserDisplayName;
}

ViewModel: 视图模型:

namespace Travel_Comp.ViewModels
{
    public sealed class MenuViewModel : INotifyPropertyChanged
    {
        //Azure sync process
        ServerManager manager;
        public event PropertyChangedEventHandler PropertyChanged;


        public MenuViewModel()
        {
            //Initial set of UserDisplayNm 
            this.UserDisplayNm = Strings.UserDisplayName;
        }

        async void LoadAsync()
        {
            try
            {
                //Run process to populate Strings.UserDisplayNm, set Syncitems to true to sync with Server
                foreach (var user in await manager.GetUsersAsync(syncItems: true))
                {
                    Strings.UserDisplayName = user.FirstName;
                }
            }
            catch (Exception e)
            {
                Console.WriteLine($"Error while retrieving user name: {e}");
            }
        }

        public string UserDisplayNm
        {
            set
            {
                if (Strings.UserDisplayName != value)
                {
                    value = Strings.UserDisplayName;

                    if (PropertyChanged != null)
                    {
                        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(UserDisplayNm)));
                    }
                }
            }
            get
            {
                return "Welcome, " + Strings.UserDisplayName;
            }
        }

    }
}

View: 视图:

protected override async void OnAppearing()
{
    base.OnAppearing();

    ViewModels.MenuViewModel.LoadAsync();

}

So if you're looking some guidance for MVVM, you should know that usually you put your dependencies in your view model constructor, here your Azure service. 因此,如果您正在寻找有关MVVM的指导,则应该知道通常将依赖项放在视图模型构造函数中,这里是Azure服务。

Also you could use a existing MVVM framework that will make things easy for you, like Prism or FreshMVVM . 另外,您可以使用现有的MVVM框架,如PrismFreshMVVM ,它将使您的工作变得容易。

But if you want to go for full vanilla you can also call your vm code from the view code behind. 但是,如果您想使用完整的香草,也可以从后面的视图代码中调用您的vm代码。

So I'm suggesting this modification to your MenuViewModel : 所以我建议对您的MenuViewModel此修改:

private IAzureService _azureService;
private string _userDisplayNm;

public MenuViewModel(IAzureService azureService)
{
    _azureService = azureService;
}

public string UserDisplayNm
{
    get
    {
        return _userDisplayNm;
    }
    set
    {
        if (_userDisplayNm != value)
        {
            _userDisplayNm = value;
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(UserDisplayNm)));
        }
    }
}

public async void LoadAsync()
{
    try
    {
        UserDisplayNm = await _azureService.GetUserName();
    }
    catch (Exception exception)
    {
        Debug.WriteLine($"Error while retrieving user name: {exception}")
    }
}

Then in you view code behind: 然后在您后面查看代码:

void OnAppearing()
{
    _menuViewModel.LoadAsync();
}

To resolve the question: Inaccessible due to its protection level , you can try to add the public access modifier before the function of LoadAsync . 解决此问题: Inaccessible due to its protection level ,您可以尝试在LoadAsync函数之前添加public访问修饰符。

public async void LoadAsync(){
   //....
  }

And I have created a simple demo to simulate your code. 我创建了一个简单的演示来模拟您的代码。

The main code is: 主要代码是:

 public sealed class TestModel: INotifyPropertyChanged
{

 //*******************************************
    string _userDisplayName;

    public string UserDisplayName {
        set { SetProperty(ref _userDisplayName, value); }

        get { return _userDisplayName; }
    }

    public async void LoadAsync()
    {
        try
        {
            UserDisplayName = "updated value: Angela";

            Strings.UserDisplayName = UserDisplayName;
        }
        catch (Exception exception)
        {
            Debug.WriteLine($"Error while retrieving user name: {exception}");
        }
    }


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

xaml XAML

 <Label Text="{Binding UserDisplayName }" BackgroundColor="Yellow" 
        VerticalOptions="Center" HorizontalOptions="Fill"  HeightRequest="50" />
 <Button  Text="update the Label value"  Clicked="Button_Clicked"/>

And use like this: 像这样使用:

public partial class MyPage1 : ContentPage
{
    TestModel model;

    public MyPage1 ()
    {
        InitializeComponent ();

        model = new TestModel();
        BindingContext = model;       
    }

    private void Button_Clicked(object sender, EventArgs e)
    {
        model.LoadAsync();
    }
}

The effect is: 效果是: 在此处输入图片说明

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM