ViewModel的第二个视图无法刷新

时间:2016-07-29 14:16:32

标签: c# wpf binding

我有这个有趣的案例: 一个相当大的应用程序的页面有自己的ViewModel。部分数据来自完全不同的Manager类。我的ViewModel可以访问这些数据。每次manager类更新一个值时,它都会发送一个Mediator消息。 ViewModel获取消息并调用OnPropertyChanged(nameof(ManagerdataData))。 ManagerData属性只有一个获取Manager.Data的getter。所以Page更新了它的东西。到目前为止,该系统仍在运作。

现在我打开一个获得System.Window的MessageBox-thing(它是MessageBox.DataContext = this.ViewModel)。对于UI它也有效。我的所有ManagerData都被加载并显示在不同的Bindings中。 但: OnPropertyChanged似乎没有任何效果。 每次这个特殊的Mediator-Message来自Manager,然后ViewModel使用OnNotificationChanged时,Page会刷新,Binding-values会重新加载并从Manager获取新数据。但MessageBox并不具备相同的ViewModel。

有没有人知道如何做到这一点?

我想,也许它是我的ViewModel的另一个实例(副本)。所以我问是否显示弹出窗口,获取正确类型的最顶层窗口并尝试调用包含这些OnNotificationChanged值的方法。首先它崩溃了,因为我在错误的线程。使用调度程序会冻结整个应用程序。

有什么想法吗?复制代码......并不容易,因为项目非常大......

编辑: 好的,所以这里是代码:

视图模型:

// Inside the constructor. Registers for Mediator-message. The manager sends it when a value is set.
Mediator.Register(MediatorMessage.BackendServerCheckRefreshed, () => RefreshServerCheckUi());

// Method of the Mediator Message
public void RefreshServerCheckUi()
{
    OnPropertyChanged(nameof(BackendServers));
    OnPropertyChanged(nameof(BackendServersCommonStatus));
}

// Properties that get stuff from the manager
public BackendServerStatus BackendServersCommonStatus
{
    get
    {
        return backendServerManager.CommonStatus;
    }
}

public BackendServer[] BackendServers
{
    get
    {
        return backendServerManager.BackendServers;
    }
}

此页面的用户界面:

<!--Style definition-->
<Style TargetType="Image" x:Key="BackendServerAvailability">
    <Style.Triggers>
        <DataTrigger Binding="{Binding BackendServersCommonStatus}"
                     Value="{x:Static server:BackendServerStatus.NotAvailable}">
            <Setter Property="Source" Value="inactive.png" />
        </DataTrigger>
        <DataTrigger Binding="{Binding BackendServersCommonStatus}"
                     Value="{x:Static server:BackendServerStatus.Available}">
            <Setter Property="Source" Value="active.png" />
        </DataTrigger>
    </Style.Triggers>
    <Style.Setters>
        <Setter Property="Source" Value="default.png" />
    </Style.Setters>
</Style>

<!--Image-->
<Image Style="{StaticResource BackendServerAvailability}" />

此绑定有效。刷新值时,它会发送Mediator消息,调用OnPropertyChanged,然后Icon获取其图像。

现在是棘手的部分:

// This is how I call the MessageBox from the ViewModel
this.MessageService.ShowBackendServerCheckInfo(this);

MessageBoxService:

// All MessageBoxes are created like this.
public void ShowBackendServerCheckInfo(ViewModel viewModel)
{
    Action action = new Action(() =>
    {
        var args = new MessageBoxEventArgs()
        {
            View = new BackendServerCheckMessageBox(),
            ViewModel = viewModel
        };

        OnNewMessageBox(this, args);
    });
    this.ShowMessageBox(action);
}

// And then called like this:
private void ShowMessageBox(Action action)
{
    /* We need to create the view in the same thread as main application. */
    Application.Current.Dispatcher.Invoke(DispatcherPriority.Normal, action);
}

最后在MainWindow.xaml.cs中:

private async void MessageService_OnNewMessageBox(object sender, MessageBoxEventArgs e)
{
    // Some Semaphore work here..

    var messageBox = e.View;
    messageBox.DataContext = e.ViewModel;
    messageBox.Owner = this;

    messageBox.ShowDialog();

    // Release Semaphore
}

MessageBox UI:

<ItemsControlItemsSource="{Binding BackendServers}">
    <ItemsControl.ItemTemplate>
        <DataTemplate>
            <Stackpanel>
                <TextBlock Text="{Binding Url}" />
                <TextBlock Text="{Binding Port}" />
                <Image Style="{StaticResource BackendServerAvailability}" />
            </Stackpanel>
        </DataTemplate>
    </ItemsControl.ItemTemplate>
</ItemsControl>

        <!--Buttons-->
<Stackpanel>
    <Button Style="{StaticResource SimpleButtonStyle}"
            Command="{Binding CloseBackendServerCheckCommand}"
            CommandParameter="{Binding ElementName=BackendServerMsgBox}">
        <TextBlock Foreground="White" Margin="2" FontSize="14"
                   Text="{x:Static const:Resources.ButtonOk}" />
    </Button>
    <Button Style="{StaticResource SimpleButtonStyle}"
            Command="{Binding RestartBackendServerCheckCommand}">
        <TextBlock Foreground="White" Margin="2" FontSize="14"
                   Text="{x:Static const:Resources.ButtonRefresh}" />
    </Button>
</StackPanel>

所以这就是所有相关的代码。当绑定不起作用时,我根本没有任何值,按钮也不起作用。 Close-Button发送其Window作为参数,然后args-Window-Object将被关闭。如果Bindings和东西不起作用,什么都不会发生。 到目前为止我修好了。现在我看到了所有的价值观。但是当后台检查发送一条服务器不可用的消息时,弹出图像不会在应用程序映像执行时刷新。

我的猜测是,将所有内容传递给EventArgs可能会产生一些副本,因此绑定到正确的ViewModel实例会丢失...所以我会对此弹出窗口进行异常处理并直接在我的ViewModel中创建它。无论如何,它比任何&#34;通常&#34;只是向你抛出一些东西的弹出窗口,然后用&#34; okay&#34;点击它。在这种情况下,它会更复杂。

EDIT2: Aaaand直接从ViewModel调用MessageBox并没有改变一件事。它不起作用。这是一个有超过1个Bound View的问题吗?

EDIT3: 好吧,它确实有效,当我有弹出窗口的实例并从ViewModel重新设置DataContext时。所以我必须找到一个很好的方法来获取或保留实例......

1 个答案:

答案 0 :(得分:0)

所以在摆弄后我有一个肮脏的解决方案:

// Only if the Popup is shown
if (isShowingBackendServerMessageBox)
{
    Application.Current.Dispatcher.BeginInvoke(new Action(() =>
    {
        // Get the frontmost Window
        var messageBox = Application.Current.Windows
            ?.OfType<BackendServerCheckMessageBox>()
            ?.FirstOrDefault();

        // If it is the searched MessageBox, reload all Bindings.
        if (messageBox != null)
        {
            messageBox.DataContext = null;
            messageBox.DataContext = this;
        }
    }));
}

我不喜欢这种类型的代码。但是当我需要它时,它会工作并刷新。如果有人有更好的想法,欢迎你。