简体   繁体   English

从另一个线程更新网格的可见性

[英]Update visibility of grid from another thread

The problem: 问题:

I have the following classes: 我有以下课程:

1) SMServiceClient - wrapper over RestSharp 1)SMServiceClient-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 2)ViewModel-用于存储接收到的数据和绑定的类

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 3)MainWindow.xaml.cs-应用程序的主窗口

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: 和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. 整个过程从调用LoginForm_LoginButtonClicked开始。

I want to show overlayThrobber when SMServiceClient sends request for the data. 我想在SMServiceClient发送数据请求时显示overlayThrobber 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. 我尝试了很多方法来异步显示它,例如通过Dispatcher.Invoke和Dispatcher.BeginInvoke或创建新的Thread,但没有任何效果。 I need to show/hide overlayThrobber from the handlers of RequestStarted and RequestFinished events. 我需要从RequestStarted和RequestFinished事件的处理程序中显示/隐藏overlayThrobber Maybe it doesn't work because I was trying to invoke the function of showing overlayThrobber in the handlers of SMServiceClient? 也许它不起作用,因为我试图调用在SMServiceClient的处理程序中显示overlayThrobber的功能? 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. 我通常在这里建议在视图模型上使用一个属性,该属性连接“ overlayThrobber”元素的可见性。 Then you can change this value and the UI will update automatically - this is using a standard XAML paradigm called MVVM (Model - View - ViewModel). 然后,您可以更改此值,并且UI将自动更新-这使用的是称为MVVM(模型-视图-视图模型)的标准XAML范例。

The "View" is the XAML. “视图”是XAML。 It uses databinding to bind to the ViewModel (in this case your code). 它使用数据绑定来绑定到ViewModel(在本例中为您的代码)。 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... 因此,在这种情况下,您将在视图模型上公开一个属性,我们将其称为ThrobberVisible ...

private bool _throbberVisible; 私人布尔值_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... 这还取决于您还在View Model类上创建OnPropertyChanged方法...

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. 此代码引发属性更改通知,这是UI会监听的内容。 There are a number of UI frameworks out there that make implementing this stuff child's play. 有许多UI框架可以实现这种玩法。 I use a bunch of snippets from a good mate and XAML guru Josh . 我使用了一堆好伴侣和XAML专家Josh的片段。

Now you need to alter your XAML so that the visibility of the throbber is tied to the ThrobberVisible value. 现在,您需要更改XAML,以便将rob动者的可见性与ThrobberVisible值绑定在一起。 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. 然后,你需要一个转换器从布尔真/假值转换为可见XAML /折叠的价值,比如这个类。 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. 您将定义一个静态资源作为此布尔转换器,通常在App.Xaml文件中执行此操作,因为我在各处都使用此转换器。

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. 一切就绪之后,您现在只需更改视图模型上的布尔值,就可以翻转UI元素的可见性。

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. 您所做的一切都是正确的,但是您使用Threads Dispatcher而不是UI Dispatcher来更新UI。 You can do as below to update the UI from another thread. 您可以执行以下操作以从另一个线程更新UI。 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 . 这不仅与可视性有关,如果要从另一个线程执行任何类型的UI操作,也必须执行相同的操作。 In your MainWindow.Xmal.cs declare a local dispatcher like below and use it 在您的MainWindow.Xmal.cs中声明一个如下所示的本地调度程序并使用它

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

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

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