Xamarin ActivityIndi​​cator不工作

时间:2017-09-18 16:21:44

标签: c# xaml xamarin xamarin.forms

我在使用Xamarin.Forms应用程序显示活动指示器时遇到问题。我使用XAML与代码隐藏,它绑定到视图模型。

我认为所有设置都是正确的,当我单步执行代码时,我可以看到IsBusy属性被适当地设置为True和False - 但实际的ActivityIndi​​cator根本不会显示。

可以看到我的错误吗?

Login.Xaml.Cs

using RestSharp;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using TechsportiseApp.Views;
using Xamarin.Forms;
using Xamarin.Forms.Xaml;
using TechsportiseApp.API;
using TechsportiseApp.ViewModels;
using TechsportiseApp.Models;
using Newtonsoft.Json;

namespace TechsportiseApp.Views
{
    public partial class Login : ContentPage
    {
        public Login ()
        {
            InitializeComponent ();
            var viewModel = new LoginViewModel();
            BindingContext = viewModel;
            ToolbarItems.Add(new ToolbarItem("New", "addperson.png", async () =>
            {
                await Navigation.PushAsync(new Register());
            }));

        }

        public string CleanResponse(string reason)
        {
            var str = reason;
            var charsToRemove = new string[] { "[", "]", "{", "}", "\"" };
            foreach (var c in charsToRemove)
            {
                str = str.Replace(c, string.Empty);
            }

            return str;
        }

        async void OnLogin(object sender, EventArgs e)
        {
            //Validations here
            if (email.Text == "")
            {
                await DisplayAlert("Validation Error", "You must enter an Email address", "OK");
                return;
            }
            else if (password.Text == "")
            {
                await DisplayAlert("Validation Error", "You must enter a Password", "OK");
                return;
            }
            //We are good to go
            else
            {
                this.IsBusy = true;
                string APIServer = Application.Current.Properties["APIServer"].ToString();
                var client = new RestClient(APIServer);
                var request = new RestRequest("api/account/sign-in", Method.POST);
                request.AddHeader("Content-type", "application/json");
                request.AddJsonBody(new
                                        {
                                            email = email.Text,
                                            password = password.Text
                                        }
                                    );

                var response = client.Execute(request) as RestResponse;

                this.IsBusy = false;
                //Valid response
                if (response.StatusCode.ToString() == "OK")
                {
                    var tokenobject = JsonConvert.DeserializeObject<TokenModel>(response.Content);
                    Application.Current.Properties["Token"] = tokenobject.Access_token;
                    string token = Application.Current.Properties["Token"].ToString();
                    App.Current.MainPage = new NavigationPage(new MainPage());

                }
                //Error response
                else
                {
                    var statuscode = response.StatusCode.ToString();
                    var content = response.Content;
                    var exception = response.ErrorException;
                    var error = response.ErrorMessage;
                    var statusdesc = response.StatusDescription;

                    await DisplayAlert("Login Failed", "Your login has failed. Please check your details and try again.", "OK");
                }
            }
        }
    }
}

Login.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="TechsportiseApp.Views.Login">
    <ContentPage.Padding>
        <OnPlatform x:TypeArguments="Thickness"
                    iOS="20, 40, 20, 20"
                    Android="20, 20, 20, 20"
                    WinPhone="20, 20, 20, 20" />
    </ContentPage.Padding>
    <ContentPage.Content>
        <AbsoluteLayout HorizontalOptions="FillAndExpand" VerticalOptions="FillAndExpand">
        <StackLayout VerticalOptions="FillAndExpand"
                     HorizontalOptions="FillAndExpand"
                     Orientation="Vertical"
                     AbsoluteLayout.LayoutFlags="All" AbsoluteLayout.LayoutBounds="0,0,1,1">

                        <ScrollView  Orientation = "Vertical" VerticalOptions="StartAndExpand">
                            <StackLayout VerticalOptions="FillAndExpand"
                                     HorizontalOptions="FillAndExpand"
                                     Orientation="Vertical">
                                <Image Source = "splash.png" HorizontalOptions="Center" />          
                                <Label Text="Race Director"
                                FontAttributes="Bold"
                                FontSize="Large"
                                    HorizontalTextAlignment="Center"
                                    HorizontalOptions="CenterAndExpand" />
                                <Label Text="by Techsportise"
                                    HorizontalTextAlignment="Center"
                                    HorizontalOptions="CenterAndExpand" />
                                <BoxView HeightRequest="20" HorizontalOptions="FillAndExpand" />
                                <Entry x:Name="email" Text="" Placeholder="Email address"/>
                                <Entry x:Name="password" Text="" IsPassword="true" Placeholder="Password"/>
                                <Button x:Name="loginButton" Text="Login" Clicked="OnLogin" Style="{StaticResource Buttons}"/>
                            </StackLayout>
                        </ScrollView>
                </StackLayout>
            <StackLayout IsVisible="{Binding IsBusy}" Padding="12"
                 AbsoluteLayout.LayoutFlags="PositionProportional"
                 AbsoluteLayout.LayoutBounds="0.5,0.5,-1,-1">

                <ActivityIndicator IsRunning="{Binding IsBusy}" Color ="#80000000"/>

                <Label Text="Loading..." HorizontalOptions="Center" TextColor="White"/>

            </StackLayout>
        </AbsoluteLayout>
        </ContentPage.Content>
</ContentPage>

LoginViewModel.cs

using System;
using System.Collections.Generic;
using System.ComponentModel;
namespace TechsportiseApp.ViewModels
{
    public class LoginViewModel : INotifyPropertyChanged
    {

        private bool _isBusy;
        public bool IsBusy
        {
            get { return _isBusy; }
            set
            {
                if (_isBusy == value)
                    return;

                _isBusy = value;
            }
        }

        public event PropertyChangedEventHandler PropertyChanged;

        protected virtual void OnPropertyChanged(string propertyName)
        {
            var changed = PropertyChanged;
            if (changed != null)
            {
                PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
            }
        }
    }
}

4 个答案:

答案 0 :(得分:1)

这与我合作:

private async void BtnLogin_OnClickedAsync(object sender, EventArgs e)
{
    activityIndicator.IsRunning = true;
    await Task.Delay(1000); // This Line solved the problem with me
    await LoginMethod();
    activityIndicator.IsRunning = false;
}

答案 1 :(得分:0)

您没有通知IsBusy财产的更改。

编辑代码:

<强> Login.Xaml.cs:

using RestSharp;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using TechsportiseApp.Views;
using Xamarin.Forms;
using Xamarin.Forms.Xaml;
using TechsportiseApp.API;
using TechsportiseApp.ViewModels;
using TechsportiseApp.Models;
using Newtonsoft.Json;

namespace TechsportiseApp.Views
{
    public partial class Login : ContentPage
    {
        public Login ()
        {
            InitializeComponent ();
            var viewModel = new LoginViewModel();
            BindingContext = viewModel;
            ToolbarItems.Add(new ToolbarItem("New", "addperson.png", async () =>
            {
                await Navigation.PushAsync(new Register());
            }));

        }

        public string CleanResponse(string reason)
        {
            var str = reason;
            var charsToRemove = new string[] { "[", "]", "{", "}", "\"" };
            foreach (var c in charsToRemove)
            {
                str = str.Replace(c, string.Empty);
            }

            return str;
        }
    }
}

<强> Login.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="TechsportiseApp.Views.Login">
    <ContentPage.Padding>
        <OnPlatform x:TypeArguments="Thickness"
                    iOS="20, 40, 20, 20"
                    Android="20, 20, 20, 20"
                    WinPhone="20, 20, 20, 20" />
    </ContentPage.Padding>
    <ContentPage.Content>
        <AbsoluteLayout HorizontalOptions="FillAndExpand" VerticalOptions="FillAndExpand">
            <StackLayout VerticalOptions="FillAndExpand"
                         HorizontalOptions="FillAndExpand"
                         Orientation="Vertical"
                         AbsoluteLayout.LayoutFlags="All" AbsoluteLayout.LayoutBounds="0,0,1,1">
                <ScrollView  Orientation = "Vertical" VerticalOptions="StartAndExpand">
                    <StackLayout VerticalOptions="FillAndExpand"
                             HorizontalOptions="FillAndExpand"
                             Orientation="Vertical">
                        <Image Source = "splash.png" HorizontalOptions="Center" />          
                        <Label Text="Race Director"
                        FontAttributes="Bold"
                        FontSize="Large"
                            HorizontalTextAlignment="Center"
                            HorizontalOptions="CenterAndExpand" />
                        <Label Text="by Techsportise"
                            HorizontalTextAlignment="Center"
                            HorizontalOptions="CenterAndExpand" />
                        <BoxView HeightRequest="20" HorizontalOptions="FillAndExpand" />
                        <Entry x:Name="email" Text="{Binding Email}" Placeholder="Email address"/>
                        <Entry x:Name="password" Text="{Binding Password}" IsPassword="true" Placeholder="Password"/>
                        <Button x:Name="loginButton" Text="Login" Command="{Binding OnLoginCommand}" Style="{StaticResource Buttons}"/>
                    </StackLayout>
                </ScrollView>
            </StackLayout>
            <StackLayout IsVisible="{Binding IsBusy}" 
                         Padding="12"
                         AbsoluteLayout.LayoutFlags="PositionProportional"
                         AbsoluteLayout.LayoutBounds="0.5,0.5,1,1">

                <ActivityIndicator IsRunning="{Binding IsBusy}" Color ="#80000000"/>

                <Label Text="Loading..." HorizontalOptions="Center" TextColor="White"/>

            </StackLayout>
        </AbsoluteLayout>
        </ContentPage.Content>
</ContentPage>

查看型号:

using System.ComponentModel;
using RestSharp;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using TechsportiseApp.Views;
using Xamarin.Forms;
using Xamarin.Forms.Xaml;
using TechsportiseApp.API;
using TechsportiseApp.ViewModels;
using TechsportiseApp.Models;
using Newtonsoft.Json;


namespace TechsportiseApp.ViewModels
{
    public class LoginViewModel : INotifyPropertyChanged
    {

        public LoginViewModel()
        {
            OnLoginCommand = new Command(ExecuteOnLogin);
        }

        private bool _isBusy;
        public bool IsBusy
        {
            get { return _isBusy; }
            set
            {
                if (_isBusy == value)
                    return;

                _isBusy = value;
                OnPropertyChanged("IsBusy");
            }
        }

        private string _email;
        public string Email
        {
            get { return _email; }
            set
            {
                if (_email == value)
                    return;

                _email = value;
                OnPropertyChanged("Email");
            }
        }

        private bool _password;
        public bool Password
        {
            get { return _password; }
            set
            {
                if (_password == value)
                    return;

                _password = value;
                OnPropertyChanged("Password");
            }
        }

        private async void ExecuteOnLogin()
        {
            //Validations here
            if (Email == "")
            {
                Device.BeginInvokeOnMainThread(() => App.Current.MainPage.DisplayAlert("Validation Error", "You must enter an Email address", "OK"));
                return;
            }
            else if (Password == "")
            {
                Device.BeginInvokeOnMainThread(() => App.Current.MainPage.DisplayAlert("Validation Error", "You must enter a Password", "OK"));
                return;
            }
            //We are good to go
            else
            {
                Device.BeginInvokeOnMainThread(() => IsBusy = true);
                string APIServer = Application.Current.Properties["APIServer"].ToString();
                var client = new RestClient(APIServer);
                var request = new RestRequest("api/account/sign-in", Method.POST);
                request.AddHeader("Content-type", "application/json");
                request.AddJsonBody(new
                                        {
                                            email = Email,
                                            password = Password
                                        }
                                    );

                var response = client.Execute(request) as RestResponse;

                Device.BeginInvokeOnMainThread(() => IsBusy = false);
                //Valid response
                if (response.StatusCode.ToString() == "OK")
                {
                    var tokenobject = JsonConvert.DeserializeObject<TokenModel>(response.Content);
                    Application.Current.Properties["Token"] = tokenobject.Access_token;
                    string token = Application.Current.Properties["Token"].ToString();
                    App.Current.MainPage = new NavigationPage(new MainPage());

                }
                //Error response
                else
                {
                    var statuscode = response.StatusCode.ToString();
                    var content = response.Content;
                    var exception = response.ErrorException;
                    var error = response.ErrorMessage;
                    var statusdesc = response.StatusDescription;

                    Device.BeginInvokeOnMainThread(() => App.Current.MainPage.DisplayAlert("Login Failed", "Your login has failed. Please check your details and try again.", "OK"));
                }
            }
        }

        public event PropertyChangedEventHandler PropertyChanged;

        protected virtual void OnPropertyChanged(string propertyName)
        {
            var changed = PropertyChanged;
            if (changed != null)
            {
                PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
            }
        }

        public Command OnLoginCommand {get;}
    }
}

答案 2 :(得分:0)

<强> [UPDATE] 也许你应该分开责任。创建一个名为ViewModelBase的类,实现INotifyPropertyChanged,如下所示:

public class ViewModelBase : INotifyPropertyChanged
{
    bool isBusy;

    /// <summary>
    /// Gets or sets a value indicating whether this instance is busy.
    /// </summary>
    /// <value><c>true</c> if this instance is busy; otherwise, <c>false</c>.</value>
    public bool IsBusy
    {
        get { return isBusy; }
        set
        {
            SetProperty(ref isBusy, value);
        }
    }


    /// <summary>
    /// Sets the property.
    /// </summary>
    /// <returns><c>true</c>, if property was set, <c>false</c> otherwise.</returns>
    /// <param name="backingStore">Backing store.</param>
    /// <param name="value">Value.</param>
    /// <param name="propertyName">Property name.</param>
    /// <param name="onChanged">On changed.</param>
    /// <typeparam name="T">The 1st type parameter.</typeparam>
    protected bool SetProperty<T>(
        ref T backingStore, T value,
        [CallerMemberName]string propertyName = "",
        Action onChanged = null)
    {
        if (EqualityComparer<T>.Default.Equals(backingStore, value))
            return false;

        backingStore = value;
        onChanged?.Invoke();
        OnPropertyChanged(propertyName);
        return true;
    }

    /// <summary>
    /// Occurs when property changed.
    /// </summary>
    public event PropertyChangedEventHandler PropertyChanged;
    /// <summary>
    /// Raises the property changed event.
    /// </summary>
    /// <param name="propertyName">Property name.</param>
    protected void OnPropertyChanged([CallerMemberName]string propertyName = "") => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}

然后创建另一个名为LoginViewModel的类,它从ViewModelBase延伸出来:

public class LoginViewModel : ViewModelBase
{
    public ICommand LoginCommand { get; set; }

    #region Properties
    private string _email;

    public string Email
    {
        get { return _email; }
        set { SetProperty(ref _email, value); }
    }

    private string _password;

    public string Password
    {
        get { return _password; }
        set { SetProperty(ref _password, value); }
    }

    #endregion

    public LoginViewModel()
    {
        LoginCommand = new Command(Login);
    }

    public string CleanResponse(string reason)
    {
        var str = reason;
        var charsToRemove = new string[] { "[", "]", "{", "}", "\"" };
        foreach (var c in charsToRemove)
        {
            str = str.Replace(c, string.Empty);
        }

        return str;
    }

    private async void Login()
    {
       //Validations here
        if (Email == "")
        {
            await DisplayAlert("Validation Error", "You must enter an Email address", "OK");
            return;
        }
        else if (Password == "")
        {
            await DisplayAlert("Validation Error", "You must enter a Password", "OK");
            return;
        }
        //We are good to go
        else
        {
            IsBusy = true;
            string APIServer = Application.Current.Properties["APIServer"].ToString();
            var client = new RestClient(APIServer);
            var request = new RestRequest("api/account/sign-in", Method.POST);
            request.AddHeader("Content-type", "application/json");
            request.AddJsonBody(new
                                    {
                                        email = Email,
                                        password = Password                                        }
                                );

            var response = client.Execute(request) as RestResponse;

            IsBusy = false;
            //Valid response
            if (response.StatusCode.ToString() == "OK")
            {
                var tokenobject = JsonConvert.DeserializeObject<TokenModel>(response.Content);
                Application.Current.Properties["Token"] = tokenobject.Access_token;
                string token = Application.Current.Properties["Token"].ToString();
                App.Current.MainPage = new NavigationPage(new MainPage());

            }
            //Error response
            else
            {
                var statuscode = response.StatusCode.ToString();
                var content = response.Content;
                var exception = response.ErrorException;
                var error = response.ErrorMessage;
                var statusdesc = response.StatusDescription;

                await DisplayAlert("Login Failed", "Your login has failed. Please check your details and try again.", "OK");
            }
        }
    }

}

你的观点是这样的:

<?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="TechsportiseApp.Views.Login">
<ContentPage.Padding>
    <OnPlatform x:TypeArguments="Thickness"
                iOS="20, 40, 20, 20"
                Android="20, 20, 20, 20"
                WinPhone="20, 20, 20, 20" />
</ContentPage.Padding>
<ContentPage.Content>
    <AbsoluteLayout HorizontalOptions="FillAndExpand" VerticalOptions="FillAndExpand">
    <StackLayout VerticalOptions="FillAndExpand"
                 HorizontalOptions="FillAndExpand"
                 Orientation="Vertical"
                 AbsoluteLayout.LayoutFlags="All" AbsoluteLayout.LayoutBounds="0,0,1,1">

                    <ScrollView  Orientation = "Vertical" VerticalOptions="StartAndExpand">
                        <StackLayout VerticalOptions="FillAndExpand"
                                 HorizontalOptions="FillAndExpand"
                                 Orientation="Vertical">
                            <Image Source = "splash.png" HorizontalOptions="Center" />          
                            <Label Text="Race Director"
                            FontAttributes="Bold"
                            FontSize="Large"
                                HorizontalTextAlignment="Center"
                                HorizontalOptions="CenterAndExpand" />
                            <Label Text="by Techsportise"
                                HorizontalTextAlignment="Center"
                                HorizontalOptions="CenterAndExpand" />
                            <BoxView HeightRequest="20" HorizontalOptions="FillAndExpand" />
                            <Entry x:Name="email" Text="{Binding Email}" Placeholder="Email address"/>
                            <Entry x:Name="password" Text="{Binding Password}" IsPassword="true" Placeholder="Password"/>
                            <Button x:Name="loginButton" Text="Login" Command="{Binding LoginCommand}" Style="{StaticResource Buttons}"/>
                        </StackLayout>
                    </ScrollView>
            </StackLayout>
        <StackLayout IsVisible="{Binding IsBusy}" Padding="12"
             AbsoluteLayout.LayoutFlags="PositionProportional"
             AbsoluteLayout.LayoutBounds="0.5,0.5,-1,-1">

            <ActivityIndicator IsRunning="{Binding IsBusy}" Color ="#80000000"/>

            <Label Text="Loading..." HorizontalOptions="Center" TextColor="White"/>

        </StackLayout>
    </AbsoluteLayout>
    </ContentPage.Content>

using RestSharp;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using TechsportiseApp.Views;
using Xamarin.Forms;
using Xamarin.Forms.Xaml;
using TechsportiseApp.API;
using TechsportiseApp.ViewModels;
using TechsportiseApp.Models;
using Newtonsoft.Json;

namespace TechsportiseApp.Views
{
    public partial class Login : ContentPage
    {
        LoginViewModel viewModel;
        public Login ()
        {
            BindingContext = viewModel = new LoginPageViewModel();
            InitializeComponent ();
            ToolbarItems.Add(new ToolbarItem("New", "addperson.png", async () =>
            {
                 await Navigation.PushAsync(new Register());
            }));

         }
    }
}

我尝试过这个解决方案,但效果很好......

答案 3 :(得分:0)

我知道这很老,但这也许可以帮助某人。我也有ActivityIndi​​cator无法显示的问题。

为了使它正常工作,我做了一些事情。 在我的ContentPage上,它包含一个ContentPage.Content元素。删除此标签后,它开始使用下面的代码。

我的代码背后的绑定属性:

public partial class Login : INotifyPropertyChanged
{
    private bool _busy = true;
    public event PropertyChangedEventHandler PropertyChanged;

    public bool Busy
    {
        get { return _busy; }
        set
        {
            _busy = value;
            RaisePropertyChanged("Busy");
        }
    }

    private bool NotBusy => !Busy;

    public Login()
    {
        InitializeComponent();
        BindingContext = this;
        Busy = false;
    }

    private async void BtnLogin_OnClicked(object sender, EventArgs e)
    {
        Busy = true;

        await Task.Delay(120); // gives UI a chance to show the spinner
        // do async login here
        var cts = new CancellationTokenSource();
        var ct = cts.Token;
        var response = Task.Factory.StartNew(() => YourAuthenticationClass.YourLoginMethod(txtUserId.Text, txtPassword.Text).Result, ct);
        // check response, handle accordingly
    }

    private void RaisePropertyChanged(string propName)
    {
        System.Diagnostics.Debug.WriteLine("RaisePropertyChanged('" + propName + "')");
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propName));
    }
}

ContentPage 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="MyApp.Login">
<StackLayout Margin="10,10,10,0">
    <Image Source="mylogo.png"
            HorizontalOptions="CenterAndExpand"></Image>
    <Entry x:Name="txtUserId"
            Placeholder="Login Id"></Entry>
    <Entry x:Name="txtPassword"
            Placeholder="Password"
            IsPassword="True">
    </Entry>
    <Button Text="Login"
        Clicked="BtnLogin_OnClicked"
        IsEnabled="{Binding NotBusy}"></Button>
    <ActivityIndicator
                    IsRunning="{Binding Busy}"
                    IsVisible="{Binding Busy}"
                    IsEnabled="True"
                    AbsoluteLayout.LayoutFlags="PositionProportional"
                    AbsoluteLayout.LayoutBounds="0.5,0.5,-1,-1"
                    VerticalOptions="Center"
                    HorizontalOptions="Center">
        <ActivityIndicator.WidthRequest>
            <OnPlatform x:TypeArguments="x:Double">
                <On Platform="Android">100</On>
            </OnPlatform>
        </ActivityIndicator.WidthRequest>
        <ActivityIndicator.Color>
            <OnPlatform x:TypeArguments="Color">
                <On Platform="Android">#ff0000</On>
            </OnPlatform>
        </ActivityIndicator.Color>
    </ActivityIndicator>
</StackLayout>
</ContentPage>