我想更新我的用户界面,但即使调用了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;
}
答案 0 :(得分:4)
哇,这是一个非常人为的单例模式实现。
首先,在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实例单例作为只读属性来使代码工作在MainPageViewModel上并直接绑定到如下:
<TextBlock Text="{Binding Path=Session.Latitude, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"/>
当然,这意味着您的UI直接写入系统中的单例组件,在该组件中可能不希望实现INotifyPropertyChanged。
正如所建议的那样,我强烈建议重新架构和删除单例和/或在系统组件之间进行消息传递。
答案 1 :(得分:2)
Christian,我已经敲定了使用Caliburn Micro的事件聚合器和IoC容器来删除依赖关系的示例,同时提供系统组件之间的通信。你可以找到它here。
需要注意的要点:
LocationService将IEventAggregator
作为构造函数参数,并且只有一个名为Initialize
的公共方法。调用Initialize
时,会启动计时器以每三秒钟获取当前位置(存根),并将包含该位置的LocationChanged
消息发布到IEventAggregator
。
ShellViewModel也将IEventAggregator
作为构造函数参数,但也实现了接口IHandle<LocationChanged>
。当调用OnActivate
方法(在视图模型绑定到视图后由Caliburn Micro调用)时,ShellViewModel调用Subscribe
传递IEventAggregator
的方法this
作为参数。 IEventAggregator查找参数实现的IHandle<>
接口,并确保在发布泛型类型的消息(在我们的示例中为LocationChanged
)时,实现类型的Handle
方法(在我们的例子中,调用ShellViewModel)。在ShellViewModel的Handle
方法中,我们使用Dispatcher
在UI线程上调用方法调用,然后更新Location属性以反映我们刚收到的新位置。
ShellView将两个文本框绑定到视图模型的Location属性,分别表示Latitude(Location.Y)和Longitude(Location.X)。
在AppBootstrapper中IEventAggregator
和ILocationService
类型使用IoC容器注册为'Singleton'类型(此处我使用的是Caliburn的默认SimpleContainer
) 。这意味着只会创建此类型的一个实例,并减少自己实现单例(反)模式的需要。最后,在OnStartup方法中,从容器中检索ILocationService
并调用initialize方法以确保它在调用LocationChange
方法之前开始发布DisplayRootViewFor<IShell>
消息(这会导致Caliburn实例化ShellViewModel视图模型,然后找到,实例化并绑定ShellView视图。
如果您运行应用程序,您将看到屏幕上每三秒钟显示一个随机位置。这是在ShellViewModel
不需要直接引用ILocationService
且没有任何(手动)单例的情况下实现的。
希望它有所帮助。
答案 2 :(得分:0)
这不是一个更简单的单身人士吗?
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;
}
}