5

当我的 ViewModel 的一个属性被更新时,其他属性被异步更新。

待办事项.cshtml:

@page "/todo"

<h1>Todo (@todos.Count(todo => !todo.IsDone))</h1>

<ul>
    @foreach (var todo in todos)
    {
        <li>
            <input type="checkbox" bind="@todo.IsDone" />
            <input bind="@todo.Title" />
        </li>
    }
</ul>

<input placeholder="Something todo" bind="@newTodo"/>
<button onclick="@AddTodo">Add todo</button>

@functions {
    IList<TodoItem> todos = new List<TodoItem>();


    string newTodo;
    void AddTodo()
    {
        if (!string.IsNullOrWhiteSpace(newTodo))
        {
            todos.Add(new TodoItem { Title = newTodo });
            newTodo = string.Empty;
        }
    }
}

TodoItem.cs:

public class TodoItem
{
    private bool _isDone;

    public string Title { get; set; }
    public bool IsDone
    {
        get => _isDone;
        set
        {
            _isDone = value;
            Task.Run(() =>
            {
                //Simulate work
                System.Threading.Thread.Sleep(TimeSpan.FromSeconds(2));
                //Update property
                Title = Title + " - Done";
            });
        }
    }
}

在同步(没有 Task.Run)中,这项工作正常,但在异步中,UI 不会更新。

我需要解释要更新的 UI StateHasChanged()https ://github.com/aspnet/Blazor/issues/1413

但是我不能在 TodoItem 中调用这个方法(而且我不希望 TodoItem 知道 Blazor 组件)。

您有更新 UI 的解决方案吗?

4

2 回答 2

7

您应该执行以下操作:

  1. 在你的类中定义一个动作委托:

     public event Action OnChange;
    
  2. 在这个类中定义一个方法NotifyStateChanged()如下:

     private void NotifyStateChanged() => OnChange?.Invoke();
    

    此方法触发OnChange事件。在完成它所做的任何任务后,您应该从您的逻辑中调用此方法。

  3. 在你的 todo 组件中,将StateHasChanged方法添加到你的类中使用的事件委托中TodoItem

     @functions
     {
         protected override void OnInit()
         {
             state.OnChange += StateHasChanged;
         }
     }
    
于 2018-11-18T22:29:32.630 回答
5

简单的答案是“修改你的 var 后就开火StateHasChanged();” :

        Task.Run(() =>
        {
            //Simulate work
            System.Threading.Thread.Sleep(TimeSpan.FromSeconds(2));
            //Update property
            Title = Title + " - Done";
            StateHasChanged();
        });

因为您的方法是异步的,所以重写为:

        Task.Run(async () =>  //<--here async
        {
            //Simulate async work
            Task.Run( async () => {
                await Task.Run( () => {} ); //<--- await
                System.Threading.Thread.Sleep(TimeSpan.FromSeconds(2));
            });
            //Update property
            Title = Title + " - Done";
            StateHasChanged();
        });

为了避免反模式并编写干净的代码,您的 ModelView 可能有一个公共事件来通知 UI 它已更改,只需将此事件在 UI 上连接到StateHasChanged();.

我在这里写了修改后的 Blazor Counter 示例:

@page "/counter"

<h1>Counter</h1>

<p>Current count: @currentCount</p>

<button class="btn btn-primary" onclick="@IncrementCount">
    Click me @s <!-- here var -->
</button>

@functions {
    int currentCount = 0;

    string s = "";

    void IncrementCount()
    {
        currentCount++;
        Task.Run(() =>
            {
                //Simulate work
                Task.Run( async () => {
                    await Task.Run( () => {} );
                    System.Threading.Thread.Sleep(TimeSpan.FromSeconds(2));}
                        );
                //Update property
                s = s + " - Done";
                StateHasChanged();
            });
        }
    }
}

已编辑

StateHasChanged不再支持从线程调用。只是改变:

    Task.Run(() =>
    {
        //Simulate work
        System.Threading.Thread.Sleep(TimeSpan.FromSeconds(2));
        //Update property
        Title = Title + " - Done";
        StateHasChanged();
    });

经过

    Invoke( () =>
    {
       ...
于 2018-11-19T07:42:41.743 回答