如果属性不同,则订阅属性上的不同事件已更改

时间:2016-02-01 21:52:03

标签: c# wpf inotifypropertychanged

我有一个类Step,它有一个Task,List的集合。 步骤具有属性状态,时间。任务也具有相同的属性。当任务中的任何人更改其时间或状态时,需要更新状态和步骤时间的值。 为此,我在Step类中为每个任务添加处理程序。

 private void AddHandlers()
        {
            foreach (Task tsk in Tasks)
            {
                tsk.PropertyChanged += HandleStatusChanged;

                tsk.PropertyChanged += HandleTimeChanged;
            }
        }
    private void HandleStatusChanged(object sender, EventArgs e)
        {
            UpdateStepStatusFromTasks();

        }
        private void HandleTimeChanged(object sender, EventArgs e)
        {
            UpdateStepTimesFromTasks();

        }

 private void UpdateStepTimesFromTasks()
        {
        // logic for calculating Time for Step

        }

        private void UpdateStepStatusFromTasks()
        {

// logic for calculating Status for Step

        }

这是Task中的Property更改事件处理程序  公共事件PropertyChangedEventHandler PropertyChanged;

    protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
    {
        var handler = PropertyChanged;
        if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));

    }

我的问题是,即使我只更改任务时间,它也会调用处理程序状态和时间,因为他们在任务上订阅了相同的属性更改事件。

我如何根据调用的属性分割属性更改事件并确保只调用相应的处理程序而不是同时调用它们?

很抱歉,如果这听起来很愚蠢,但我有点像WPF的初学者。

此致 P

3 个答案:

答案 0 :(得分:2)

每个事件都有“访问者”添加或删除。类似于获取/设置属性的东西。此访问者可以向您显示事件的性质。每个事件都有一个InvocationList,它表示在引发事件时它将通知的对象集合。使用此访问器,您可以更好地控制通知和不通知的内容。订阅该事件时,订阅的对象将插入到“调用”列表中。

由于您为两个事件订阅了相同的对象,因此您将触发两次事件。

您唯一能做的就是检查已更新的属性的名称

public void ChangedHandler(object sender, PropertyChangedEventArgs  e)
{
    if(e.PropertyName=="Time"){//do something}
    else if (e.PropertyName == "Date") {doSomething}
}

由于你正在处理WPF,我在这里看到一个奇怪的模式。您正在通过各种方法提升事件。您应该从您希望通知发生的属性中引发事件,该属性绑定到控件。

public class MyVM
{
    private string _status = "status1";
    public string Status
    {
        get
        {
            return _status;
        }
        set
        {
            if(_status!=value)
            {
                _status =value
                OnPropertyChanged("Status");
            }
        }
    }
}

您可以使用“nameof”,baseClasses或MethorVeawers等FODY

等各种内容对此进行改进

答案 1 :(得分:2)

您需要检查传入的参数的参数以获取属性的名称。

首先摆脱双重订阅。

private void AddHandlers()
{
    foreach (Task tsk in Tasks)
    {
        tsk.PropertyChanged += HandlePropertyChanged;
    }
}

然后为您的活动使用正确的签名,以便获得正确类型的事件参数。

private void HandlePropertyChanged(object sender, PropertyChangedEventArgs e)
{

既然我们已PropertyChangedEventArgs而非EventArgs,我们可以检查PropertyName属性并调用所需的方法。

private void HandlePropertyChanged(object sender, PropertyChangedEventArgs e)
{
    switch(e.PropertyName)
    {
        case "Status":
            UpdateStepStatusFromTasks();
            break;
        case "Time":
            UpdateStepTimesFromTasks();
            break;
     }
}

由于您需要处理更多属性,您只需将它们添加到switch语句即可。

P.S。您可以使用BindingList<Task>作为保存任务的集合,而不是手动订阅每个Task,然后您可以订阅ListChanged事件,如果任何事件将被引发列表中的项目会引发PropertyChanged(请务必启用RaiseListChangedEvents并检查ListChangedEventArgs.ListChangedType是否等于ListChangedType.ItemChanged)。

答案 2 :(得分:0)

所以,这里显而易见的是你将两个处理程序附加到``事件,所以一切都被处理了两次。它只需要订阅一次。

但是,我不想使用代码在所有地方反弹而制作许多复杂的方法,而是更喜欢使用Microsoft的Reactive Extensions(Rx) - NuGet“Rx-Main” - 对事件做任何事情。在学习了几个基本操作符之后,它确实使得处理事件变得更加容易。

Rx过于简单化了,LINQ for Events。它允许您使用查询来处理事件而不是枚举。它创造了可观察性。

首先,我会创建这个observable:

var tpns = // IObservable<{anonymous}>
    from t in Tasks.ToObservable()
    from ep in Observable.FromEventPattern<
            PropertyChangedEventHandler, PropertyChangedEventArgs>(
        h => t.PropertyChanged += h,
        h => t.PropertyChanged -= h)
    select new { Task = t, ep.EventArgs.PropertyName };

此查询基本上采用Tasks列表,并在单个observable中转换每个任务的所有PropertyChanged事件,当该任务具有属性更改时返回每个Task并且PropertyName已更改的任务。

现在很容易创建一些按PropertyName过滤并返回Task的更多可观察量:

IObservable<Task> statusChanges =
    from tpn in tpns
    where tpn.PropertyName == "Status"
    select tpn.Task;

IObservable<Task> timeChanges =
    from tpn in tpns
    where tpn.PropertyName == "Time"
    select tpn.Task;

这些应该很容易理解。

现在订阅每个(基本上像附加到事件):

IDisposable statusSubscription =
    statusChanges
        .Subscribe(task => UpdateStepStatusFromTasks());

IDisposable timeSubscription =
    timeChanges
        .Subscribe(task => UpdateStepTimesFromTasks());

您会注意到每个订阅都是IDisposable。您不必使用-=运算符从事件中分离,而只需在订阅上调用.Dispose(),并为您分离所有基础事件处理程序。

现在我建议您更改AddHandlers方法以返回IDisposable。然后调用AddHandlers的代码可以处理处理程序 - 如果需要 - 以确保在退出之前可以清理。

所以完整的代码看起来像这样:

private IDisposable AddHandlers()
{
    var tpns = // IObservable<{anonymous}>
        from t in Tasks.ToObservable()
        from ep in Observable.FromEventPattern<
                PropertyChangedEventHandler, PropertyChangedEventArgs>(
            h => t.PropertyChanged += h,
            h => t.PropertyChanged -= h)
        select new { Task = t, ep.EventArgs.PropertyName };

    IObservable<Task> statusChanges =
        from tpn in tpns
        where tpn.PropertyName == "Status"
        select tpn.Task;

    IObservable<Task> timeChanges =
        from tpn in tpns
        where tpn.PropertyName == "Time"
        select tpn.Task;

    IDisposable statusSubscription =
        statusChanges
            .Subscribe(task => UpdateStepStatusFromTasks());

    IDisposable timeSubscription =
        timeChanges
            .Subscribe(task => UpdateStepTimesFromTasks());

    return new CompositeDisposable(statusSubscription, timeSubscription);
}

唯一的新功能是CompositeDisposable将两个IDiposable订阅加入一个IDisposable

这种方法的优点在于,现在大多数代码都可以很好地存储在单个方法中。这样做很容易理解和维护 - 至少在一小段学习曲线之后。 : - )