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.
注意要点:
LocationService 将 IEventAggregator
作为构造函数参数,并有一个名为 Initialize
的 public 方法。当 Initialize
被调用时,一个定时器被启动以每三秒获取当前位置(停止)并发布一个包含该位置的 LocationChanged
消息到 IEventAggregator
.
ShellViewModel 也将 IEventAggregator
作为构造函数参数,但也实现了接口 IHandle<LocationChanged>
。当调用 OnActivate
方法时(这是在视图模型绑定到视图后由 Caliburn Micro 调用的),ShellViewModel 调用 IEventAggregator
传递 this
的 Subscribe
方法作为参数。 IEventAggregator 查找参数实现的 IHandle<>
接口,并确保在发布通用类型的消息(在我们的例子中为 LocationChanged
)时,实现类型的 Handle
方法(在我们的例子中,调用了 ShellViewModel)。在 ShellViewModel 的 Handle
方法中,我们使用 Dispatcher
在 UI 线程上调用方法调用,然后更新 Location 属性 以反映我们刚刚获得的新位置收到。
ShellView将两个文本框绑定到视图模型的Location属性,分别代表纬度(Location.Y)和经度(Location.X ) 分别。
在 AppBootstrapper 中,IEventAggregator
和 ILocationService
类型在 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;
}
}
我想更新我的 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.
注意要点:
LocationService 将
IEventAggregator
作为构造函数参数,并有一个名为Initialize
的 public 方法。当Initialize
被调用时,一个定时器被启动以每三秒获取当前位置(停止)并发布一个包含该位置的LocationChanged
消息到IEventAggregator
.ShellViewModel 也将
IEventAggregator
作为构造函数参数,但也实现了接口IHandle<LocationChanged>
。当调用OnActivate
方法时(这是在视图模型绑定到视图后由 Caliburn Micro 调用的),ShellViewModel 调用IEventAggregator
传递this
的Subscribe
方法作为参数。 IEventAggregator 查找参数实现的IHandle<>
接口,并确保在发布通用类型的消息(在我们的例子中为LocationChanged
)时,实现类型的Handle
方法(在我们的例子中,调用了 ShellViewModel)。在 ShellViewModel 的Handle
方法中,我们使用Dispatcher
在 UI 线程上调用方法调用,然后更新 Location 属性 以反映我们刚刚获得的新位置收到。ShellView将两个文本框绑定到视图模型的Location属性,分别代表纬度(Location.Y)和经度(Location.X ) 分别。
在 AppBootstrapper 中,
IEventAggregator
和ILocationService
类型在 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;
}
}