C# + XAML - UI 更改后未更新

C# + XAML - UI didn't get updated after changes

我想更新我的 UI,但它没有得到更新,即使调用了 PropertyChanged 事件。在启动时我得到第一个值,但在更改后它不会更新。

在调试时,我可以看到值得到更新并且 PropertyChanged 事件被触发,但是 getter 没有被调用。

(顺便说一句:我对 C# 和 M-V-VM 概念都比较陌生。)

XAML

<Page.DataContext>
    <vm:MainPageViewModel x:Name="ViewModel" />
</Page.DataContext>
...
<TextBlock x:Name="LatitudeVar" Text="{Binding LatitudeVar, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" Width="79" Canvas.Left="189" Canvas.Top="38" />

C# (MainPageViewModel)

public class MainPageViewModel : ViewModelBase, INotifyPropertyChanged
{
    private Session _session;
    public MainPageViewModel()
    {
        _session = Session.Instance;
    }

    public static readonly MainPageViewModel Instance = new MainPageViewModel();

    public override async Task OnNavigatedToAsync(object parameter, NavigationMode mode, IDictionary<string, object> suspensionState)
    {

        // App just started, so we get GPS access and eventually initialize the client
        Session.InitializeClient();
        await StartGpsDataService();
        await Task.CompletedTask;
    }

    ...

    private string _latitude;
    public string LatitudeVar
    {
        get { return _session.Latitude; }
        set { _session.Latitude = value; NotifyPropertyChanged(); }
    }

    ...

    public async Task StartGpsDataService()
    {
        await Session.InitializeDataUpdate();
    }

     public new event PropertyChangedEventHandler PropertyChanged;

    [Annotations.NotifyPropertyChangedInvocator]
    protected virtual void NotifyPropertyChanged([CallerMemberName] string propertyName = null)
    {
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
    }

C#(会话)

public class Session : INotifyPropertyChanged
{
    public static void InitializeClient()
    {
        MainPageViewModel mpvm = new MainPageViewModel();
        _mpvm = MainPageViewModel.Instance;
    }

    private static Session _instance;
    public static Session Instance
    {
        get
        {
            if (_instance == null)
            {
                _instance = new Session();
            }
            return _instance;
        }
    }


    private static Session _sc;
    internal static Session Sc
    {
        get { return _sc; }
        set { _sc = value; }
    }


    private static MainPageViewModel _mpvm;

    private string _latitude;
    public string Latitude
    {
        get { return _latitude; }
        set
        {
            if (_latitude == value) return; _latitude = value; RaiseOnPropertyChanged("Latitude"); }
    }

    ...

     public void UpdateGpsData(Geopoint point, Geopoint geopointOld)
    {
        _mpvm.LatitudeVar = point.Position.Latitude.ToString();
    }

    public static async Task InitializeDataUpdate()
    {
       Sc = Session.Instance;
       Sc.StartTime = DateTime.Now;
       Sc.GetPosition(Geoposition.Coordinate.Point);
    }

    public new event PropertyChangedEventHandler PropertyChanged;

    [NotifyPropertyChangedInvocator]
    protected virtual void RaiseOnPropertyChanged([CallerMemberName] string propertyName = null)
    {
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
    }

我之前尝试过的一些方法:

C# (MainPageViewModel)

private string _latitude;
public string LatitudeVar
{
    get { return _latitude; }
    set { _latitude = value; NotifyPropertyChanged("LatitudeVar"); }
}

结果:未显示值。

C#(会话)

public void UpdateGpsData(Geopoint point, Geopoint geopointOld)
{
    Latitude = point.Position.Latitude.ToString();
}

结果: 值在启动时显示,但未更新。

编辑:找到解决方案: 一些代码排序,感谢提示 ;) 现在我的 Session.cs 是一个模型。所有相关方法现在都在 ViewModel 中。我注意到,只有一个实例存在:

public MainPageViewModel()
{
   _session = Session.Instance;
   _instance = this;
}

哇哦,这是一个非常人为的单例模式实现。

首先,在 Session.InitializeClient 中创建的 MainPageViewModel 实例不是视图正在使用的实例。 XAML

<Page.DataContext>
    <vm:MainPageViewModel x:Name="ViewModel" />
</Page.DataContext>

将实例化 MainPageViewModel 的新实例,因此代码

public void UpdateGpsData(Geopoint point, Geopoint geopointOld)
{
    _mpvm.LatitudeVar = point.Position.Latitude.ToString();
}

不会影响视图。

虽然我强烈建议您取消对 Session 的单例尝试并考虑使用消息传递模式在系统组件之间进行通信,但您应该能够通过将 Session 实例单例公开为在 MainPageViewModel 上只读 属性 并直接绑定到它,如下所示:

<TextBlock Text="{Binding Path=Session.Latitude, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"/>

当然,这意味着您的 UI 正在直接写入您系统中的单例组件,其中可能不希望实现 INotifyPropertyChanged。

按照建议,我强烈建议重新构建和删除单例 and/or 包含系统组件之间的消息传递。

Christian,我举了一个例子,使用 Caliburn Micro 的事件聚合器和 IoC 容器来消除依赖性,同时提供系统组件之间的通信。你可以找到它 here.

注意要点:

  1. LocationServiceIEventAggregator 作为构造函数参数,并有一个名为 Initialize 的 public 方法。当 Initialize 被调用时,一个定时器被启动以每三秒获取当前位置(停止)并发布一个包含该位置的 LocationChanged 消息到 IEventAggregator.

  2. ShellViewModel 也将 IEventAggregator 作为构造函数参数,但也实现了接口 IHandle<LocationChanged>。当调用 OnActivate 方法时(这是在视图模型绑定到视图后由 Caliburn Micro 调用的),ShellViewModel 调用 IEventAggregator 传递 thisSubscribe 方法作为参数。 IEventAggregator 查找参数实现的 IHandle<> 接口,并确保在发布通用类型的消息(在我们的例子中为 LocationChanged)时,实现类型的 Handle 方法(在我们的例子中,调用了 ShellViewModel)。在 ShellViewModel 的 Handle 方法中,我们使用 Dispatcher 在 UI 线程上调用方法调用,然后更新 Location 属性 以反映我们刚刚获得的新位置收到。

  3. ShellView将两个文本框绑定到视图模型的Location属性,分别代表纬度(Location.Y)和经度(Location.X ) 分别。

  4. AppBootstrapper 中,IEventAggregatorILocationService 类型在 IoC 容器中注册为 'Singleton' 类型(这里我使用 Caliburn 的默认值SimpleContainer)。这意味着只会创建一个这种类型的实例,并减少了自己实现单例(反)模式的需要。最后,在 OnStartup 方法中,从容器中检索 ILocationService 并调用初始化方法以确保它在调用 DisplayRootViewFor<IShell> 方法之前开始发布 LocationChange 消息(这导致 Caliburn 实例化ShellViewModel 视图模型,然后定位、实例化和绑定 ShellView 视图。

如果您 运行 该应用程序,您将看到屏幕上每三秒显示一次随机位置。这是在不需要 ShellViewModel 直接引用 ILocationService 并且没有任何(手动)单例的情况下实现的。

希望对您有所帮助。

这不是更简单的单例吗?

public class MyViewModel
{
    static MyViewModel()
    {
        Instance = new MyViewModel();
    }

    public static MyViewModel Instance { get; }

    private MyViewModel()
    {
        // TODO
    }

    public override async Task OnNavigatedToAsync(object parameter, NavigationMode mode, IDictionary<string, object> suspensionState)
    {
        await Task.CompletedTask;
    }
}