简体   繁体   中英

Update visibility of grid from another thread

The problem:

I have the following classes:

1) SMServiceClient - wrapper over RestSharp

public class SMServiceClient
{
    public delegate void AuthorizationSucceededHandler(SMServiceEventArgs args);
    public event AuthorizationSucceededHandler AuthorizationSucceeded;

    public delegate void AuthorizationFailedHandler(SMServiceEventArgs args);
    public event AuthorizationFailedHandler AuthorizationFailed;

    public delegate void RequestStartedHandler(SMServiceEventArgs args);
    public event RequestStartedHandler RequestStarted;

    public delegate void RequestFinishedHandler(SMServiceEventArgs args);
    public event RequestFinishedHandler RequestFinished;


    private const string baseUrl = "http://10.0.0.6:4000";
    private RestClient client;

    public SMServiceClient()
    {
        client = new RestClient(baseUrl);
        client.CookieContainer = new CookieContainer();
        client.FollowRedirects = false;
    }

    public void AuthUser(string username, string password)
    {
        RequestStarted(new SMServiceEventArgs());
        var request = new RestRequest("/api/login", Method.POST);
        request.AddParameter("username", username);
        request.AddParameter("password", password);
        var response = client.Execute<User>(request);
        if (response.StatusCode == HttpStatusCode.Unauthorized)
        {
            AuthorizationFailed(new SMServiceEventArgs("Credential are incorrect!"));
        }
        else {
            AuthorizationSucceeded(new SMServiceEventArgs(response.Data));
        }
        RequestFinished(new SMServiceEventArgs());
    }
}

2) ViewModel - class for storing received data and bindings

public class ViewModel : INotifyPropertyChanged
{
    private SMServiceClient _client;
    public SMServiceClient client { get { return _client; } }

    private Credentials _currentLogging;
    public Credentials currentLogging
    {
        get { return _currentLogging; }
        set
        {
            if (_currentLogging != value)
            {
                _currentLogging = value;
                OnPropertyChanged(new PropertyChangedEventArgs("currentLogging"));
            }
        }
    }

    public ViewModel() 
    {
        _client = new SMServiceClient();
        currentLogging = new Credentials();
    }

    public void AuthUser()
    {
        _client.AuthUser(currentLogging.login, currentLogging.password);
    }

    public event PropertyChangedEventHandler PropertyChanged;
    public void OnPropertyChanged(PropertyChangedEventArgs e)
    {
        PropertyChangedEventHandler handler = PropertyChanged;
        if (handler != null)
            handler(this, e);
    }
}

3) MainWindow.xaml.cs - the main window of application

public partial class MainWindow : MetroWindow
{
    public ViewModel _viewModel;
    public MainWindow()
    {
        InitializeComponent();
        _viewModel = new ViewModel();
        _viewModel.client.AuthorizationSucceeded += ServiceClient_AuthorizationSucceeded;
        _viewModel.client.AuthorizationFailed += ServiceClient_AuthorizationFailed;
        _viewModel.client.RequestStarted += ServiceClient_RequestStarted;
        _viewModel.client.RequestFinished += ServiceClient_RequestFinished;

        this.DataContext = _viewModel;
    }

    void ServiceClient_RequestStarted(SMServiceEventArgs args)
    {
        this.Dispatcher.BeginInvoke(new Action(() => { overlayThrobber.Visibility = System.Windows.Visibility.Visible; }), DispatcherPriority.Normal);
    }

    void ServiceClient_RequestFinished(SMServiceEventArgs args)
    {
        this.Dispatcher.BeginInvoke(new Action(() => { overlayThrobber.Visibility = System.Windows.Visibility.Collapsed; }), DispatcherPriority.Normal);
    }

    void ServiceClient_AuthorizationSucceeded(SMServiceEventArgs args)
    {
        _viewModel.loggedUser = args.loggedUser;

        Storyboard storyboard = new Storyboard();
        TimeSpan duration = new TimeSpan(0, 0, 0, 0, 600);
        DoubleAnimation animation = new DoubleAnimation();
        animation.From = 1.0;
        animation.To = 0.0;
        animation.Duration = new Duration(duration);

        Storyboard.SetTargetName(animation, "overlayControl");
        Storyboard.SetTargetProperty(animation, new PropertyPath(Control.OpacityProperty));
        storyboard.Children.Add(animation);
        storyboard.Completed += storyboard_Completed;
        storyboard.Begin(this);
    }

    void ServiceClient_AuthorizationFailed(SMServiceEventArgs args)
    {
        loginErrorBlock.Text = args.message;
    }

    private void LoginForm_LoginButtonClicked(object sender, RoutedEventArgs e)
    {
        _viewModel.AuthUser();
    }

    void storyboard_Completed(object sender, EventArgs e)
    {
        overlayControl.Visibility = System.Windows.Visibility.Collapsed;
    }
}

And Xaml:

<controls:MetroWindow x:Class="ScheduleManager.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:sm="clr-namespace:ScheduleManager"
        xmlns:controls="clr-namespace:MahApps.Metro.Controls;assembly=MahApps.Metro"
        xmlns:extWpf="clr-namespace:Xceed.Wpf.Toolkit;assembly=Xceed.Wpf.Toolkit"
        Title="Schedule Manager" Height="500" Width="600" MinHeight="500" MinWidth="600">
    <Grid x:Name="mainGrid">
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="Auto"/>
            <ColumnDefinition Width="*"/>
        </Grid.ColumnDefinitions>
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto"/>
            <RowDefinition Height="*"/>
        </Grid.RowDefinitions>
        <Grid x:Name="overlayThrobber" Grid.Column="1" Grid.Row="1" Background="Gray" Opacity="0.7" Panel.ZIndex="2000" Visibility="Collapsed">
            <Grid.ColumnDefinitions>
                <ColumnDefinition Width="*"/>
                <ColumnDefinition Width="Auto"/>
                <ColumnDefinition Width="*"/>
            </Grid.ColumnDefinitions>
            <Grid.RowDefinitions>
                <RowDefinition Height="*"/>
                <RowDefinition Height="Auto"/>
                <RowDefinition Height="*"/>
            </Grid.RowDefinitions>
            <controls:ProgressRing x:Name="processThrobber" Grid.Column="1" Grid.Row="1" Foreground="White" IsActive="True" Opacity="1.0"/>
        </Grid>
    </Grid>
</controls:MetroWindow>

The whole process starts from invocation of LoginForm_LoginButtonClicked.

I want to show overlayThrobber when SMServiceClient sends request for the data. I have tried a lot of ways to show it asynchronously, eg via Dispatcher.Invoke and Dispatcher.BeginInvoke or creating new Thread, but nothing works. I need to show/hide overlayThrobber from the handlers of RequestStarted and RequestFinished events. Maybe it doesn't work because I was trying to invoke the function of showing overlayThrobber in the handlers of SMServiceClient? What you can advice me?

Roman,

What I'd usually suggest here is using a property on the view model that the visibility of the "overlayThrobber" element is connected to. Then you can change this value and the UI will update automatically - this is using a standard XAML paradigm called MVVM (Model - View - ViewModel).

The "View" is the XAML. It uses databinding to bind to the ViewModel (in this case your code). The Model is generally the back-end, in this case you could consider the data you're retrieving over the wire to be the model.

So in this case you would expose a property on your view model, let's call it ThrobberVisible...

private bool _throbberVisible;

public bool ThrobberVisible
{
  get { return _throbberVisible; }
  set
  {
    if (value != _throbberVisible)
    {
      _throbberVisible = value;
      this.OnPropertyChanged("ThrobberVisible");
    }
  }
}

This relies on you also creating an OnPropertyChanged method on your View Model class...

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

This code raises the property change notification, which is what the UI will listen to. There are a number of UI frameworks out there that make implementing this stuff child's play. I use a bunch of snippets from a good mate and XAML guru Josh .

Now you need to alter your XAML so that the visibility of the throbber is tied to the ThrobberVisible value. Typically you'll do the following...

... Visibility={Binding ThrobberVisible, Converter={StaticResource boolToVisible}}

That hooks the visibility to the property on the view model. Then you'll need a converter to convert from a Boolean true/false value to the XAML Visible/Collapsed value, such as this class. You'll define a static resource to be this Boolean converter, I typically do this in my App.Xaml file as I use this converter all over the place.

With all that in place, you should now be able to flip the visibility of a UI element simply by changing the Boolean value on your view model.

Hope this helps! Apologies if the code does not compile, I've just typed it in freehand. :-)

What ever your doing is correct, but your using Threads Dispatcher instead of UI Dispatcher to Update the UI. You can do as below to update the UI from another thread. This is not only with respect to Visibility, if you want to perform any kind of UI action from another thread you have to do the same . In your MainWindow.Xmal.cs declare a local dispatcher like below and use it

public partial class MainWindow : MetroWindow
{
 public ViewModel _viewModel;
 private  Dispatcher dispathcer = Dispatcher.CurrentDispatcher;
 public MainWindow()
 {
    InitializeComponent();
    _viewModel = new ViewModel();
    _viewModel.client.AuthorizationSucceeded += ServiceClient_AuthorizationSucceeded;
    _viewModel.client.AuthorizationFailed += ServiceClient_AuthorizationFailed;
    _viewModel.client.RequestStarted += ServiceClient_RequestStarted;
    _viewModel.client.RequestFinished += ServiceClient_RequestFinished;

    this.DataContext = _viewModel;
}

void ServiceClient_RequestStarted(SMServiceEventArgs args)
{
   dispathcer.Invoke(new Action(() => { overlayThrobber.Visibility = System.Windows.Visibility.Visible; }), DispatcherPriority.Normal);
}

void ServiceClient_RequestFinished(SMServiceEventArgs args)
{
    dispathcer.Invoke(new Action(() => { overlayThrobber.Visibility = System.Windows.Visibility.Collapsed; }), DispatcherPriority.Normal);
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

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