Wpf 后台工作进程完成后绑定按钮未启用

Wpf 后台工作进程完成后绑定按钮未启用,wpf,multithreading,backgroundworker,Wpf,Multithreading,Backgroundworker,我有一个后台工作进程,它开始为我们的系统配置一个新的客户端。以下是DoWork方法的外观: ProvisioningManager manager = new ProvisioningManager(false) { }; System.Windows.Application.Current.Dispatcher.Invoke((Action)(() => { this.MaxSteps = manager.MaxProgress; })); manager.StatusUpd

我有一个后台工作进程,它开始为我们的系统配置一个新的客户端。以下是DoWork方法的外观:

ProvisioningManager manager = new ProvisioningManager(false)
{
};

System.Windows.Application.Current.Dispatcher.Invoke((Action)(() =>
{
    this.MaxSteps = manager.MaxProgress;
}));

manager.StatusUpdated += new ProvisioningManager.StatusUpdatedHandler(manager_StatusUpdated);
manager.TaskCompleted += new ProvisioningManager.TaskCompleteHandler(manager_TaskCompleted);

manager.ProvisionClient();
while (!manager.Completed)
{
    System.Threading.Thread.Sleep(100 * 60);
}
基本上,它创建了一个经理,负责与为客户提供服务的不同子系统进行对话

现在,我有了provisioning manager的状态更新事件和已完成事件。激发TaskCompleted事件时,我希望能够在显示对象上设置属性,以便启用向导中的“完成”按钮:

void manager_TaskCompleted(object sender, ProvisioningManager.Task taskType)
{
    System.Windows.Application.Current.Dispatcher.Invoke((Action)(() =>
    {
        this.ProvisioningComplete = true;
    }));
}
按钮的XAML如下所示:

<wizard:WizardPage Header="Provisioning Client..."
                               ShowBack="False" 
                               AllowBack="False" 
                               AllowFinish="{Binding Source={StaticResource ResourceKey=dataObject}, Path=ProvisioningComplete}" 
                               Loaded="Provisioning_Loaded">
</wizard:WizardPage>
那也不行。什么给了我

更新 以下是为显示对象请求的静态资源实例化:

<Window.Resources>
    <ObjectDataProvider x:Key="dataObject" ObjectType="{x:Type winDO:NewClientWizardDO}" />
</Window.Resources>

抱歉,如果我不理解某些内容,但是ProvisioningComplete属性是否标记为“volatile”?如果不是的话,这可能就是问题所在。

所以我无法确切地找出我出现这个问题的原因。我尝试将焦点设置为窗口、按钮等。我尝试了多种方式让视图知道viewmodel已更新。基本上,我在网上找到的每一个建议都不起作用。它几乎像一只虫子

我团队的一个聪明人建议在窗口上假装鼠标点击。他的想法是,由于只需在屏幕上单击鼠标即可激活按钮,因此假装按下按钮也会产生同样的效果。我认为(并且认为)这种黑客行为是荒谬的。我确实试过了,只是想看看是否可以称之为“解决方案”

嗯,它起作用了。我们的另一个向导(不是AvalonWizard,而是一个自主开发的向导)也有同样的问题。我认为在后台线程更新绑定到UI的对象后,窗口重画的方式可能存在一些潜在问题

无论如何,我发现解决这个问题的方法是使用以下hack-tastic代码

//import user32.dll and setup the use of the mouse_event method
[DllImport("user32.dll", CharSet = CharSet.Auto, CallingConvention = CallingConvention.StdCall)]

/// <summary>
/// Watches for properties to change on the data object, mainly the ProvisioningComplete method
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
void DataObject_PropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
{
    switch (e.PropertyName)
    {
        case "ProvisioningComplete":
            //if the provisioning is completed then we need to make the finish button selectable.
            if (this.DataObject.ProvisioningComplete)
            {
                System.Windows.Application.Current.Dispatcher.Invoke((Action)(() =>
                {
                    //give the window focus
                    this.Focus();
                    //update the layout
                    WizardPageProvisioningClient.UpdateLayout();
                    //fake mouse click 50 pixels into the window
                    mouse_event(MOUSEEVENTF_LEFTDOWN | MOUSEEVENTF_LEFTUP, (uint)(this.Left + 50), (uint)(this.Top + 50), 0, 0);
                }));
            }
            break;
    }
}
//导入user32.dll并设置鼠标事件方法的使用
[DllImport(“user32.dll”,CharSet=CharSet.Auto,CallingConvention=CallingConvention.StdCall)]
/// 
///监视数据对象上要更改的属性,主要是ProvisioningComplete方法
/// 
/// 
/// 
void DataObject_PropertyChanged(对象发送方,System.ComponentModel.PropertyChangedEventArgs e)
{
开关(如PropertyName)
{
案例“供应完成”:
//如果配置完成,那么我们需要选择finish按钮。
if(this.DataObject.ProvisioningComplete)
{
System.Windows.Application.Current.Dispatcher.Invoke((操作)(()=>
{
//给窗口焦点
这是Focus();
//更新布局
WizardPageProvisioningClient.UpdateLayout();
//假鼠标点击窗口50像素
鼠标事件(MOUSEEVENTF_LEFTDOWN | MOUSEEVENTF_LEFTUP,(uint)(this.Left+50),(uint)(this.Top+50),0,0);
}));
}
打破
}
}
我已经在窗口不是活动窗口以及用户选择离开窗口时测试了这一点。当窗口处于非活动状态时,焦点方法似乎可以解决此问题。我们的QA团队还没有对UI进行完整的测试,所以我不能说是否有任何情况下它不起作用,但这似乎是我迄今为止提出的最佳解决方案


如果有人对导致按钮不更新的原因有更好的了解,我愿意接受任何其他建议。

您可以发布静态资源“StaticResourceKey=dataObject”的声明吗?@Murven,请求的代码已添加到问题中。感谢您研究此问题。我假设NewClientWizardo实现INotifyPropertyChanged,并且property ProvisioningComplete触发PropertyChangedEvent。你能确认或张贴该课程的代码吗?很抱歉,我也应该要这个,如果不确定的话很难说。我可能已经找到了一个黑客补丁。我在视图中添加了一些代码,使用PropertyChanged事件作为显示对象。当它发现ProvisioningComplete已更改且为true时,我将获得向导页面并调用“UpdateLayout”。这似乎是可行的,但我还没有完全测试过。很抱歉,这篇文章有双重含义,但“UpdateLayout”不起作用。我想是的,但我可能已经从一个屏幕移到了另一个窗口,然后将焦点移回了该窗口,从而使按钮启用。仍然坚持这一点…我不知道你所说的标记一些不稳定的东西是什么意思。这是绑定标记还是属性上的属性?这里有一个示例和简短的解释似乎有意义,但遗憾的是,由于屏幕上没有用户活动,即使将volatile关键字添加到属性后面的字段中,按钮也不会将状态从禁用变为启用。我开始认为这可能是一个AvalonWizard错误。
public bool ProvisioningComplete
{
    get { return this._ProvisioningComplete; }
    set
    {
        this._ProvisioningComplete = value;
        this.NotifyPropertyChanged("ProvisioningComplete");
    }
}

protected void NotifyPropertyChanged(params string[] propertyNames)
{
    if (this.PropertyChanged != null)
    {
        foreach (string propertyName in propertyNames)
            this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
    }
}
//import user32.dll and setup the use of the mouse_event method
[DllImport("user32.dll", CharSet = CharSet.Auto, CallingConvention = CallingConvention.StdCall)]

/// <summary>
/// Watches for properties to change on the data object, mainly the ProvisioningComplete method
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
void DataObject_PropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
{
    switch (e.PropertyName)
    {
        case "ProvisioningComplete":
            //if the provisioning is completed then we need to make the finish button selectable.
            if (this.DataObject.ProvisioningComplete)
            {
                System.Windows.Application.Current.Dispatcher.Invoke((Action)(() =>
                {
                    //give the window focus
                    this.Focus();
                    //update the layout
                    WizardPageProvisioningClient.UpdateLayout();
                    //fake mouse click 50 pixels into the window
                    mouse_event(MOUSEEVENTF_LEFTDOWN | MOUSEEVENTF_LEFTUP, (uint)(this.Left + 50), (uint)(this.Top + 50), 0, 0);
                }));
            }
            break;
    }
}