As is well known, CM doesn't support passing a object of complex type through NavigationService like MVVM Light. So I searched for a workaround and did it like this.
There are two viewmodels: MainPageViewModel and SubPageViewModel.
I first defined 3 classes, namely GlobalData, SnapshotCache and StockSnapshot. StockSnapshot is the type of which the object I want to pass between the 2 viewmodels.
public class SnapshotCache : Dictionary<string, StockSnapshot>
{
public StockSnapshot GetFromCache(string key)
{
if (ContainsKey(key))
return this[key];
return null;
}
}
public class GlobalData
{
private GlobalData()
{
}
private static GlobalData _current;
public static GlobalData Current
{
get
{
if (_current == null)
_current = new GlobalData();
return _current;
}
set { _current = value; }
}
private SnapshotCache _cachedStops;
public SnapshotCache Snapshots
{
get
{
if (_cachedStops == null)
_cachedStops = new SnapshotCache();
return _cachedStops;
}
}
}
public class StockSnapshot
{
public string Symbol { get; set; }
public string Message { get; set; }
}
Next, I call the navigation service on MainPageViewModel like this:
StockSnapshot snap = new StockSnapshot {Symbol="1", Message = "The SampleText is here again!" };
GlobalData.Current.Snapshots[snap.Symbol] = snap;
NavigationService.UriFor<SubPageViewModel>().WithParam(p=>p.Symbol,snap.Symbol).Navigate();
And on SubPageViewModel I've got this:
private string _symbol;
public string Symbol
{
get { return _symbol; }
set
{
_symbol = value;
NotifyOfPropertyChange(() => Symbol);
}
}
public StockSnapshot Snapshot
{
get { return GlobalData.Current.Snapshots[Symbol]; }
}
And that's where the problem lies. When I run the program, I find out that it always runs to the getter of Snapshot first, when Symbol hasn't been initialized yet. So later I've tried adding some extra code to eliminate the ArgumentNullException so that it can run to the setter of Symbol and then everything goes fine except that the UI doesn't get updated anyway.
Could anyone tell me where I've got wrong?
Thx in advance!!
Why not just use:
private string _symbol;
public string Symbol
{
get { return _symbol;}
set
{
_symbol = value;
NotifyOfPropertyChange(() => Symbol);
NotifyOfPropertyChange(() => Snapshot);
}
}
public StockSnapshot Snapshot
{
get { return Symbol!=null? GlobalData.Current.Snapshots[Symbol]:null; }
}
In this case you don't try and get the data from GlobalData when Symbol is null (sensible approach anyway!) and when "Symbol" is set you call NotifyOfPropertyChange() on Snapshot to force a re-get of the property.
Related
I'm currently working on a Xamarin.iOS project that uses a web-api to gather data. However, I'm running into some problems trying to pass the user input from a textfield to the Tableview that gets the result from the api.
To do this I've followed the example on the MvvmCross documentation.
The problem is that the input from the Textfield never reaches the 'Filter' property in my TableviewController's viewmodel. I think I'm not passing the string object correctly to my IMvxNavigationService when called.
To clarify, in my UserinputViewController I'm binding the textfield's text like so:
[MvxFromStoryboard(StoryboardName = "Main")]
public partial class SearchEventView : MvxViewController
{
public SearchEventView (IntPtr handle) : base (handle)
{
}
public override void ViewDidLoad()
{
base.ViewDidLoad();
MvxFluentBindingDescriptionSet<SearchEventView, SearchEventViewModel> set = new MvxFluentBindingDescriptionSet<SearchEventView, SearchEventViewModel>(this);
set.Bind(btnSearch).To(vm => vm.SearchEventCommand);
set.Bind(txtSearchFilter).For(s => s.Text).To(vm => vm.SearchFilter);
set.Apply();
}
}
The Viewmodel linked to this ViewController looks like this:
public class SearchEventViewModel : MvxViewModel
{
private readonly IMvxNavigationService _navigationService;
private string _searchFilter;
public string SearchFilter
{
get { return _searchFilter; }
set { _searchFilter = value; RaisePropertyChanged(() => SearchFilter); }
}
public SearchEventViewModel(IMvxNavigationService mvxNavigationService)
{
this._navigationService = mvxNavigationService;
}
public IMvxCommand SearchEventCommand {
get {
return new MvxCommand<string>(SearchEvent);
}
}
private async void SearchEvent(string filter)
{
await _navigationService.Navigate<EventListViewModel, string>(filter);
}
}
And finally, TableviewController's viewmodel looks like this:
public class EventListViewModel : MvxViewModel<string>
{
private readonly ITicketMasterService _ticketMasterService;
private readonly IMvxNavigationService _navigationService;
private List<Event> _events;
public List<Event> Events
{
get { return _events; }
set { _events = value; RaisePropertyChanged(() => Events); }
}
private string _filter;
public string Filter
{
get { return _filter; }
set { _filter = value; RaisePropertyChanged(() => Filter); }
}
public EventListViewModel(ITicketMasterService ticketMasterService, IMvxNavigationService mvxNavigationService)
{
this._ticketMasterService = ticketMasterService;
this._navigationService = mvxNavigationService;
}
public IMvxCommand EventDetailCommand {
get {
return new MvxCommand<Event>(EventDetail);
}
}
private void EventDetail(Event detailEvent)
{
_navigationService.Navigate<EventDetailViewModel, Event>(detailEvent);
}
public override void Prepare(string parameter)
{
this.Filter = parameter;
}
public override async Task Initialize()
{
await base.Initialize();
//Do heavy work and data loading here
this.Events = await _ticketMasterService.GetEvents(Filter);
}
}
Whenever trying to run, the string object 'parameter' in my TableviewController's Prepare function remains 'null' and I have no idea how to fix it. Any help is greatly appreciated!
I believe the issue is with your command setup
new MvxCommand<string>(SearchEvent);
As this command is being bound to a standard UIButton. It will not pass through a parameter value of your filter but null instead. So the string parameter generic can be removed. Additionally, as you want to execute an asynchronous method I would suggest rather using MvxAsyncCommand
new MvxAsyncCommand(SearchEvent);
Then in terms of SearchEvent method you can remove the parameter. The value of filter is bound to your SearchFilter property. It is this property's value that you want to send as the navigation parameter.
private async Task SearchEvent()
{
await _navigationService.Navigate<EventListViewModel, string>(SearchFilter);
}
I am creating windows store app in VS2012 c#/xaml using WindowsStore GridApp Template.
And I am using Group and Items pages that this template has.
In Group page I am displaying a list of Rooms - datasource for this are RoomObjects
public class RoomsObject : LivingDataCommon
{
public RoomsObject()
: base(String.Empty, String.Empty)
{
}
public RoomsObject(String ID, String title)
: base(ID, title)
{ }
//adds Actors to collection of a Room, will be used for Rooms pages
private ObservableCollection<ActorsObject> _actors = new ObservableCollection<ActorsObject>();
public ObservableCollection<ActorsObject> Actors
{
get { return this._actors; }
}
}
In Item page I am displaying a list of Actors that each Room has - datasource for this are ActorsObjects
public class ActorsObject : LivingDataCommon
{
public ActorsObject()
: base(String.Empty, String.Empty)
{
}
public ActorsObject(String ID, String title, Boolean homepage,String function, RoomsObject room, double currentValue, ActorsType type, AllActors allactors)
: base(ID, title)
{
this._function = function;
this._room = room;
this._currentValue = currentValue;
this._type = type;
this._homepage = homepage;
this._all = allactors;
}
//set home page appearance
private Boolean _homepage = false;
public static Boolean Homepage = false;
//sets value of an actor
private double _currentValue;
public double CurrentValue
{
get { return this._currentValue; }
set { this.SetProperty(ref this._currentValue, value); }
}
//sets and gets function code
private string _function = string.Empty;
public string Function
{
get { return this._function; }
set { this.SetProperty(ref this._function, value); }
}
//gets room properity
private RoomsObject _room;
public RoomsObject Room
{
get { return this._room; }
set { this.SetProperty(ref this._room, value); }
}
private ActorsType _type;
public ActorsType Type
{
get { return this._type; }
set { this.SetProperty(ref this._type, value); }
}
private AllActors _all;
public AllActors All
{
get { return this._all; }
set { this.SetProperty(ref this._all, value); }
}
}
When I select an Actor in Items page my appbar appears and I need on my pinButton to allow that Actor to be displayed at Home.xaml as well.
I am assuming that I should create an empty ObservableCollection an add selected items to it, and then use that collection as data source for Home.xaml, but I am new at c#, I cant get it work..
Please any suggestions, code, or some different ways to do this?
Hmm.. just to give you some input. I would probably create a "global" static class for this, which you can access from your entire app (public static class PinnedActors). Within this class you have your static ObservableCollection.
I'm currently working on a solution that has a set of composite ViewModels that are mapped from domain models coming back from a set of data access services.
So far I've had a good amount of success with implementing INotifyPropertyChanged on the base ViewModel object and notifying the UI of changes to the property objects via property changed events.
Here's an example of a view model:
public class DisplayDataModel : INotifyPropertyChanged{
private DateTime _lastRefreshTime;
public DateTime LastRefreshTime {
get { return _lastRefreshTime; }
set {
_lastRefreshTime = value;
this.NotifyPropertyChanged(lddm => lddm.LastRefreshTime, PropertyChanged);
}
}
private string _lineStatus;
public string LineStatus {
get { return _lineStatus; }
set {
if (_lineStatus != value) {
_lineStatus = value;
this.NotifyPropertyChanged(lddm => lddm.LineStatus, PropertyChanged);
}
}
}
private ProductionBrickModel _productionBrick;
public ProductionBrickModel ProductionBrick {
get { return _productionBrick;}
set {
if (_productionBrick != value) {
_productionBrick = value;
this.NotifyPropertyChanged(lddm => lddm.ProductionBrick, PropertyChanged);
}
}
}
}
public class ProductionBrickModel{
public int? Set { get; set; }
public int? Theoretical { get; set; }
public int? Actual { get; set; }
public string LineName { get; set; }
public TimeSpan? ShiftOverage { get; set; }
public SolidColorBrush ShiftOverageBrush {
get {
if (ShiftOverage.HasValue && ShiftOverage.Value.Milliseconds < 0) {
return Application.Current.FindResource("IndicatorRedBrush") as SolidColorBrush;
}
return Application.Current.FindResource("IndicatorWhiteBrush") as SolidColorBrush;
}
}
public string ShiftOverageString { get { return ShiftOverage.HasValue ? ShiftOverage.Value.ToShortTimeSpanString() : ""; } }
}
So currently I'm firing notification events on the base model and not the production brick property, mostly because the production brick properties will be changing almost every refresh anyways.
Recently I've started cranking refresh times down to around 350ms and I'm seeing situations where the ShiftOverageBrush is changing to white for a split second even though the values are still negative.
My question is by going through and implementing INotifyPropertyChanged on the object types that make up the base view model will I gain any performance, or even possibly solve this issue? Or is this coming from something else entirely that I'm not understanding?
There are two obvious sources of inefficieny in your code:
1) ShiftOverageBrush is using FindResource every time it's called. Why not cache the brushes?
private SolidColorBrush _redBrush;
private SolidColorBrush IndicatorRedBrush
{
get{ return _redBrush ?? (_redBrush =
Application.Current.FindResource("IndicatorRedBrush") as SolidColorBrush));
}
... same for white brush
public SolidColorBrush ShiftOverageBrush {
get {
if (ShiftOverage.HasValue && ShiftOverage.Value.Milliseconds < 0) {
return IndicatorRedBrush;
}
return IndicatorWhiteBrush;
}
}
2) Using a lambda expression for NotifyPropertyChanged is convenient but is pretty slow since it uses reflection. If you're cranking up the update rate, then replace the lambdas with strings.
Suppose that the scenario doesn't allow to implement an immutable type. Following that assumption, I'd like opinions / examples on how to properly design a type that after it's consumed, becomes immutable.
public class ObjectAConfig {
private int _valueB;
private string _valueA;
internal bool Consumed { get; set; }
public int ValueB {
get { return _valueB; }
set
{
if (Consumed) throw new InvalidOperationException();
_valueB = value;
}
}
public string ValueA {
get { return _valueA; }
set
{
if (Consumed) throw new InvalidOperationException();
_valueA = value;
}
}
}
When ObjectA consumes ObjectAConfig:
public ObjectA {
public ObjectA(ObjectAConfig config) {
_config = config;
_config.Consumed = true;
}
}
I'm not satisfied that this simply works, I'd like to know if there's a better pattern (excluded, as said, making ObjectAConfig immutable by design from begin).
For example:
can make sense define a monad like Once<T> that allow the wrapped value to be initialized only once?
can make sense define a type that returns the type itself changing a private field?
What you are implementing sometimes goes under the name "popsicle immutability" - i.e. you can freeze it. Your current approach will work - indeed I use that pattern myself in numerous places.
You can probably reduce some duplication via something like:
private void SetField<T>(ref T field, T value) {
if (Consumed) throw new InvalidOperationException();
field = value;
}
public int ValueB {
get { return _valueB; }
set { SetField(ref _valueB, value); }
}
public string ValueA {
get { return _valueA; }
set { SetField(ref _valueA, value); }
}
There is another related approach, though: a builder. For example, taking your existing class:
public interface IConfig
{
string ValueA { get; }
int ValueB { get; }
}
public class ObjectAConfig : IConfig
{
private class ImmutableConfig : IConfig {
private readonly string valueA;
private readonly int valueB;
public ImmutableConfig(string valueA, int valueB)
{
this.valueA = valueA;
this.valueB = valueB;
}
}
public IConfig Build()
{
return new ImmutableConfig(ValueA, ValueB);
}
... snip: implementation of ObjectAConfig
}
Here there is a truly immutable implementation of IConfig, and your original implementation. If you want the frozen version, call Build().
Does someone know library, that reduces amounts of boilerplate code when writing object proxies?
My proxies right now look the following way and I think it's a nasty approach :)
public class SampleTenantProxy : Tenant
{
public override int? Id
{
get { return tenant.Id; }
set { tenant.Id = value; }
}
public override String Code
{
get { return tenant.Code; }
set { tenant.Code = value; }
}
public override String Name
{
get { return tenant.Name; }
set { tenant.Name = value; }
}
public override Decimal Price
{
get { return tenant.Price; }
set { tenant.Price = value; }
}
private readonly Tenant tenant;
public TenantListBoxProxy(Tenant tenant)
{
this.tenant = tenant;
}
}
Most Dependency Injection tools (such as Windsor Castle - have a look here) can do it.
Castle Dynamic Proxy -> http://www.castleproject.org/dynamicproxy/index.html