I have code that looks similar to the following
ViewModel:
public class VM : ReactiveObject
{
public double _number;
public double Number
{
get { return _number; }
set { this.RaiseAndSetIfChanged(ref _number, value); }
}
}
View:
public sealed partial class MainPage : Page, IViewFor<VM>
{
public MainPage()
{
ViewModel = new VM();
DataContext = ViewModel;
this.InitializeComponent();
this.WhenAnyValue(t => t.ViewModel.Number)
.Subscribe(n => Debug.WriteLine(n));
}
public VM ViewModel
{
get { return (VM)GetValue(ViewModelProperty); }
set { SetValue(ViewModelProperty, value); }
}
public static readonly DependencyProperty ViewModelProperty =
DependencyProperty.Register("ViewModel", typeof(VM), typeof(MainPage), new PropertyMetadata(null));
object IViewFor.ViewModel
{
get { return ViewModel; }
set { ViewModel = (VM)value; }
}
private void Button_Click(object sender, RoutedEventArgs e)
{
ViewModel.Number = ViewModel.Number + 1;
}
}
When I run it I get an ArgumentNullException on the "WhenAnyValue" line.
This seems to follow every example of usage I can find. I'm at a loss here.
Make ViewModel a DependencyProperty and your problems will go away
Related
I am tying to further understand MVVM with some example scenario. I have a rootpage with a 'maindisplay' textblock. I would like to display 'status' or 'scenarios' from activation of any form of UI eg. togglebutton on the 'maindisplay' textblock.
I am able to bind the the page navigation info in the rootpageviewmodel to the textblock. However, I am not able to achieve the result when displaying info from different page.
I have checked another post multiple-viewmodels-in-same-view & Accessing a property in one ViewModel from another it's quite similar but it didn't work.
Please help. Thanks.
While accessing the RootPageViewModel should retain the instance?
View
<TextBlock Text="{x:Bind RootViewModel.MainStatusContent, Mode=OneWay}"/>
RootPage.xaml.cs
public sealed partial class RootPage : Page
{
private static RootPage instance;
public RootPageViewModel RootViewModel { get; set; }
public RootPage()
{
RootViewModel = new RootPageViewModel();
this.InitializeComponent();
// Always use the cached page
this.NavigationCacheMode = NavigationCacheMode.Required;
}
public static RootPage Instance
{
get
{
if (instance == null)
{
instance = new RootPage();
}
return instance;
}
}
private void nvTopLevelNav_ItemInvoked(NavigationView sender, NavigationViewItemInvokedEventArgs args)
{
if (args.IsSettingsInvoked)
{
contentFrame.Navigate(typeof(SettingsPage));
RootViewModel.MainStatusContent = "Settings_Page";
}
else
{
var navItemTag = args.InvokedItemContainer.Tag.ToString();
RootViewModel.MainStatusContent = navItemTag;
switch (navItemTag)
{
case "Home_Page":
contentFrame.Navigate(typeof(HomePage));
break;
case "Message_Page":
contentFrame.Navigate(typeof(MessagePage));
break;
}
}
}
}
RootPage ViewModel:
public class RootPageViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private static RootPageViewModel instance = new RootPageViewModel();
public static RootPageViewModel Instance
{
get
{
if (instance == null)
instance = new RootPageViewModel();
return instance;
}
}
public RootPageViewModel()
{
}
private string _mainStatusContent;
public string MainStatusContent
{
get
{
return _mainStatusContent;
}
set
{
_mainStatusContent = value;
OnPropertyChanged();
}
}
protected void OnPropertyChanged([CallerMemberName] string name = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
}
}
MessagePage.xaml.cs - to access RootPage ViewModel
public sealed partial class MessagePage : Page
{
public MessagePageViewModel MessageViewModel { get; set; }
public MessagePage()
{
MessageViewModel = new MessagePageViewModel();
this.InitializeComponent();
// Always use the cached page
this.NavigationCacheMode = NavigationCacheMode.Required;
}
private void Message1_Checked(object sender, RoutedEventArgs e)
{
RootPageViewModel.Instance.MainStatusContent = "Message 1 Selected";
}
private void Message1_Unchecked(object sender, RoutedEventArgs e)
{
RootPageViewModel.Instance.MainStatusContent = "Message 1 De-Selected";
}
}
When I debug the value did write to the instance but did't update the TextBlock. Did I do anything wrong in my XAML binding?
UWP C# MVVM How To Access ViewModel from Other Page
The better way is make static variable for RootPage, but not make singleton instance for RootPage and RootPageViewModel.
For example:
public RootPage ()
{
this.InitializeComponent();
this.NavigationCacheMode = NavigationCacheMode.Required;
Instance = this;
RootViewModel = new RootPageViewModel();
}
public static RootPage Instance;
Usage
private void Message1_Checked(object sender, RoutedEventArgs e)
{
RootPage.Instance.RootViewModel.MainStatusContent = "Message 1 Selected";
}
private void Message1_Unchecked(object sender, RoutedEventArgs e)
{
RootPage.Instance.RootViewModel.MainStatusContent = "Message 1 De-Selected";
}
I'm using AvaloniaUI and making a UserControl with a StyledProperty using. I'm using MVVM.
The problem is that when I have a Binding to my styledproperty but it doesn't update.
I want to use the UserControl like this <Comp:ValueTextBlock VariableName="{Binding RobotSettingsModel.Robot_SP}"/> where VariableName uses a binding to a model that is created in the ViewModel.
The problem is that I can't seem to get the StyledProperty to work when I use a binding. When I set VariableName directly in the view it does work
// this works
<Comp:ValueTextBlock VariableName="PLC_U1_Robot_SP"/>
// this doesn't
<Comp:ValueTextBlock VariableName="{Binding RobotSettingsModel.Robot_SP}" DescriptionLocation="Left"/>
What am I doing wrong here?
The code-behind for my UserControl ValueTextBlock looks something like this:
public class ValueTextBlock : UserControl
{
private ValueTextBlockVm _viewModel;
#region --- Variable name properties ---
public static readonly StyledProperty<string> VariableNameProperty = AvaloniaProperty.Register<ValueTextBlock, string>(nameof(VariableName), defaultBindingMode: BindingMode.TwoWay, defaultValue: "UNKNOWN DProperty");
public string VariableName
{
get { return _viewModel.vmVariableName; } // the property is used in the ViewModel
set { _viewModel.vmVariableName = value; }
}
#endregion
#region constructor
public ValueTextBlock()
{
this.InitializeComponent();
// create new instance of viewmodel and attach it as DataContext
_viewModel = new ValueTextBlockVm();
this.DataContext = _viewModel;
}
private void InitializeComponent()
{
AvaloniaXamlLoader.Load(this);
}
#endregion
}
In the ViewModel for ValueTextBlock vmVariableName is done like this:
private string _vmVariableName = "UKN";
public string vmVariableName
{
get => _vmVariableName;
set => this.RaiseAndSetIfChanged(ref _vmVariableName, value);
}
When i use this UserControl and directly set VariableName in view it works, but when I use Binding it doesn't work.
This is my view:
<StackPanel>
<Comp:ValueTextBlock VariableName="PLC_U1_Robot_SP"/> <!-- directly setting VariableName works -->
<Comp:ValueTextBlock VariableName="{Binding RobotSettingsModel.Robot_SP}" DescriptionLocation="Left"/> <!-- binding VariableName to a model doesn't work -->
<TextBlock Text="{Binding RobotSettingsModel.Robot_SP}"/> <!-- binding normal text to a model does work-->
</StackPanel>
Code-behind for the view
public class ucRobotSettings : UserControl
{
private ucRobotSettingsVm _viewModel;
#region properties
public string Prefix
{
get { return _viewModel.vmPrefix; }
set { _viewModel.vmPrefix = value; }
}
public static readonly StyledProperty<string> PrefixProperty = AvaloniaProperty.Register<ucRobotSettings, string>(nameof(Prefix));
#endregion
#region constructor
public ucRobotSettings()
{
this.InitializeComponent();
// create new instance of viewmodel and attach it as DataContext
_viewModel = new ucRobotSettingsVm();
this.DataContext = _viewModel;
this.AttachedToVisualTree += ucRobotSettings_AttachedToVisualTree;
}
private void InitializeComponent()
{
AvaloniaXamlLoader.Load(this);
}
#endregion
private void ucRobotSettings_AttachedToVisualTree(object sender, VisualTreeAttachmentEventArgs e)
{
_viewModel.OnAttachedToVisualTree();
}
}
In the ViewModel a new RobotSettingsModel is made, this is what I want to bind to in the View
public class ucRobotSettingsVm : ViewModelBase
{
#region --- properties ---
private string _vmPrefix;
public string vmPrefix
{
get => _vmPrefix;
set => this.RaiseAndSetIfChanged(ref _vmPrefix, value);
}
private RobotSettingsModel _RobotSettingsModel = new RobotSettingsModel();
public RobotSettingsModel RobotSettingsModel
{
get => _RobotSettingsModel;
set => this.RaiseAndSetIfChanged(ref _RobotSettingsModel, value);
}
#endregion
public ucRobotSettingsVm() { }
public void OnAttachedToVisualTree()
{
// don't update if the prefix hasn't changed
if (RobotSettingsModel.Prefix != vmPrefix) RobotSettingsModel.Prefix = vmPrefix;
}
}
The model that is used in ucRobotSettings looks like this:
public class RobotSettingsModel : ReactiveObject
{
// unit prefix.
private string _Prefix;
public string Prefix { get => _Prefix; set { this.RaiseAndSetIfChanged(ref _Prefix, value); NotifyPropertyChanged(); } }
// values
private string _Robot_SP = "UKN";
public string Robot_SP { get => _Robot_SP; set => this.RaiseAndSetIfChanged(ref _Robot_SP, value); }
public RobotSettingsModel()
{ }
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged([System.Runtime.CompilerServices.CallerMemberName] String propertyName = "")
{
if (propertyName == "Prefix") // only update when property "Prefix changes"
{
Robot_SP = Prefix + "_" + nameof(Robot_SP);
// inform outside outside world the complete class has PropertyChanged
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
}
I have a custom Behavior class inheriting from a bindable behavior class
where I have ItemSource property and the propertyChanged event on the BindableProperty only invokes at creation time not on changed even I call RaisePorpertyChanged
BindableBehaviorClass
public abstract class BindableBehavior<T> : Behavior<T> where T : BindableObject
{
public T AssociatedObject { get; private set; }
protected override void OnAttachedTo(T visualElement)
{
base.OnAttachedTo(visualElement);
AssociatedObject = visualElement;
if (visualElement.BindingContext != null)
BindingContext = visualElement.BindingContext;
visualElement.BindingContextChanged += OnBindingContextChanged;
}
private void OnBindingContextChanged(object sender, EventArgs e)
{
OnBindingContextChanged();
}
protected override void OnDetachingFrom(T view)
{
view.BindingContextChanged -= OnBindingContextChanged;
}
protected override void OnBindingContextChanged()
{
base.OnBindingContextChanged();
BindingContext = AssociatedObject.BindingContext;
}
}
CustomListBehavior
public static readonly BindableProperty ItemsSourceProperty = BindableProperty.Create(nameof(ItemsSource),
typeof(IEnumerable<IPerson>),
typeof(CustomListBehavior),
defaultValue: null,
propertyChanged: ItemsSourceChanged);
public IEnumerable<IPerson> ItemsSource
{
get { return (IEnumerable<IPerson>)GetValue(ItemsSourceProperty); }
set { SetValue(ItemsSourceProperty, value); }
}
ViewModel
private ObservableCollection<IPreson> people = new ObservableCollection<IPerson>();
public ObservableCollection<IPerson> People
{
get => people;
set => SetProperty(ref people , value, nameof(People));
}
In my view Model I changed instead of instantiating the the local variable like
this private ObservableCollection<IPreson> people = new ObservableCollection<IPerson>();
I do it in before I assign it the new values after creation of the class.
This is my UnityResolver Class to create the instance of IUnityContainer
public sealed class UnityResolver
{
private static IUnityContainer _unityContainer;
private static volatile UnityResolver _unityresolverinstance;
private static object syncRoot = new Object();
public static IUnityContainer UnityContainerInitiation
{
get
{
if (_unityContainer == null)
{
if (_unityresolverinstance == null)
{
lock (syncRoot)
{
if (_unityresolverinstance == null)
_unityresolverinstance = new UnityResolver();
}
}
}
return UnityResolver._unityContainer;
}
}
public UnityResolver()
{
_unityContainer = new UnityContainer();
_unityContainer.RegisterType<MaintainRouteViewModel>();
}
}
Below is my Base View and Its ViewModelCode
public partial class MaintainRouteView : UserControl
{
public MaintainRouteViewModel maintainRouteViewModel = null;
IUnityContainer container;
public MaintainRouteView()
{
InitializeComponent();
container = UnityResolver.UnityContainerInitiation;
maintainRouteViewModel = container.Resolve<MaintainRouteViewModel>();
this.DataContext = maintainRouteViewModel;
}
///This button will navigate to the child view.
private void AddRoute_Click(object sender, RoutedEventArgs e)
{
pageAnimationControl.ShowPage(new AddNewRouteView());
}
}
Its ViewModel..
public class MaintainRouteViewModel : viewModelbase
{
private string _statusSuccessMessage = null;
private string _statusFailMessage =null;
private ObservableCollection<RouteDetailsModel> _routeDetailsCollection;
public ObservableCollection<RouteDetailsModel> routeDetailsCollection
{
get
{
return this._routeDetailsCollection;
}
set
{
this._routeDetailsCollection = value;
RaisePropertyChanged("routeDetailsCollection");
}
}
public string StatusSuccessMessage
{
get
{
return _statusSuccessMessage;
}
set
{
_statusSuccessMessage = value;
this.RaisePropertyChanged("StatusSuccessMessage");
}
}
public string StatusFailMessage
{
get { return _statusFailMessage; }
set
{
_statusFailMessage = value;
this.RaisePropertyChanged("StatusFailMessage");
}
}
public MaintainRouteViewModel()
{
///it will load some data to the Observablecollection
getAllCurrentRouteData();
}
}
Now Below is my Child View and its ViewModel....
public partial class AddNewRouteView : UserControl
{
public AddNewRouteView()
{
InitializeComponent();
IUnityContainer container = UnityResolver.UnityContainerInitiation;
this.DataContext = container.Resolve<AddNewRouteViewModel>();
}
}
Its ViewModel....
public class AddNewRouteViewModel : viewModelbase
{
private MaintainRouteViewModel maintainRouteViewModel;
public ICommand SaveCommand
{
get;
set;
}
[InjectionConstructor]
public AddNewRouteViewModel(MaintainRouteViewModel maintainRouteViewModel)
{
this.maintainRouteViewModel = maintainRouteViewModel;
SaveCommand = new DelegateCommand<object>((a) => ValidateNewRoute());
}
private void ValidateNewRoute()
{
bool flag = saveAndValidate();
if(flag)
{
updateRouteStatus();
}
}
public void updateRouteStatus()
{
maintainRouteViewModel.StatusSuccessMessage = "New Route successfully Added..";
}
}
}
Can Anyone Tell me how to use this way to get the same object of MaintainRouteViewModel in my Child VM Constructor So that i will show the Updated Status Message in my Base view MaintainRouteView???
*It will Work Fine If i replace my MaintainRouteView with below code :
this Is an another approach to use IOC .i previously using this in my project. it Works Fine for me but now i want to implement the same thing using Unity Container. Please Help.
public partial class MaintainRouteView : UserControl
{
public MaintainRouteViewModel maintainRouteViewModel = null;
public MaintainRouteView()
{
InitializeComponent();
maintainRouteViewModel = new MaintainRouteViewModel();
this.DataContext = maintainRouteViewModel;
}
private void AddRoute_Click(object sender, RoutedEventArgs e)
{
pageTransitionControl.ShowPage(
new AddNewRouteView
{
DataContext = new AddNewRouteViewModel(maintainRouteViewModel)
});
}
}
I am able to solve this issue using the LifeTime Management of Unity Container Register Types.
it will work fine if i tell the container to create a singleton instance of the MaintainRouteViewModel Class.
using :
container.RegisterType<MaintainRouteViewModel>(
new ContainerControlledLifetimeManager());
But it's just a workaround to get the expected result. i want to achieve it using a proper dependency injection without any singleton instance principle. Can anyone please help to provide the solution.
In my application i'm trying to load the main module trough code. Everything works up to the point of rendering and i have NO clue why it isn't rendering. The content has the right values and everything.. My guess is that something went wibbly wobbly and I seem to be missing it.
Control that holds the view
[ContentPropertyAttribute("ContentView")]
public class ContentControlExtened : ContentControl
{
public static readonly DependencyProperty ContentProperty =
DependencyProperty.Register(
"Type",
typeof(Type),
typeof(ContentControl),
new FrameworkPropertyMetadata(null, ContentTypeChanged));
public static readonly DependencyProperty ContentViewProperty =
DependencyProperty.Register(
"View",
typeof(FrameworkElement),
typeof(ContentControl),
new FrameworkPropertyMetadata(null, ContentViewChanged));
private static void ContentViewChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
//throw new NotImplementedException();
}
public ContentControlExtened()
{
this.Loaded += ContentControlLoaded;
}
private void ContentControlLoaded(object sender, RoutedEventArgs e)
{
this.LoadContent();
}
private void LoadContent()
{
UserControl u = null;
if (Type != null)
{
u = (UserControl)ServiceLocator.Current.GetInstance(this.Type);
}
u.Background = new SolidColorBrush(Color.FromRgb(0, 0, 255));
this.View = u;
}
public Type Type
{
get { return (Type)GetValue(ContentProperty); }
set { SetValue(ContentProperty, value); }
}
public FrameworkElement View
{
get { return (FrameworkElement)GetValue(ContentProperty); }
set
{
SetValue(ContentProperty, value);
}
}
}
Method in shell to load the main view of the given moduleInfo
private void OpenMainView(ModuleInfo module)
{
Type moduleType = Type.GetType(module.ModuleType);
var moduleMainViewType = moduleType.Assembly.GetType(moduleType.Namespace + ".Views.MainView");
this.ContentHolder.MainContent.Type = moduleMainViewType;
}
The contructor of the mainview is straight forward. Just standard control InitializeComponent() and Datacontext is being set trough the servicelocator.
public FrameworkElement View
{
get { return (FrameworkElement)GetValue(ContentProperty); }
set
{
SetValue(ContentProperty, value);
}
}
Here you are getting and setting the ContentProperty. It should be ContentViewProperty.