Property not being set after model is updated - c#

My application is using MVVM and exposes the properties on the model by using getter properties on the view model. The problem I am having is when I set one of my properties on the model, the property on the view model that gets the property from the model is not being updated - the get/set is never called after the property on the model is set. If I breakpoint on the ColourR of the view model, neither the get or set is hit after SelectedColour is set even though ColourR on the model is being set.
The ColourR property on the model is set by the view model when SelectedColour on the view model updates the property on the model. The text box on my view is binded to the ColourR property on the view model, which should get the value of the property on the model, but whenever this value changes it is not updating correctly.
If I bind the textbox directly to the model using {Binding LineAppearanceLayerDefinition.ColourR}, it works correctly but I would like to understand why the binding does not work with my property on the view model as I would prefer to bind this way.
Model:
public class AppearanceLayerDefinition : BindableBase
{
private string _colourR;
public string ColourR
{
get { return _colourR; }
set
{
SetProperty(ref _colourR, value);
}
}
private Color _selectedColour;
public Color SelectedColour
{
get { return _selectedColour; }
set
{
SetProperty(ref _selectedColour, value);
ColourR = value.R.ToString();
}
}
}
View Model:
public class AppearanceLayerDefinitionLineViewModel
{
public AppearanceLayerDefinitionLineViewModel(ApperanceLayerDefinition appearanceLayerDefinition)
{
LineAppearanceLayerDefinition = appearanceLayerDefinition;
}
public LineAppearanceLayerDefinition { get; private set; }
public Color SelectedColour
{
get { return LineAppearanceLayerDefinition.SelectedColour; }
set
{
LineAppearanceLayerDefinition.SelectedColour = value;
}
}
private string _colourR;
public string ColourR
{
get { return LineAppearanceLayerDefinition.ColourR; }
set
{
LineAppearanceLayerDefinition.ColourR = value;
SetProperty(ref _colourR, value);
}
}
}
XAML:
<TextBox Grid.Column="2" Style="{StaticResource AppearanceLayersTextBoxGroupStyle}" Text="{Binding ColourR}" />

Related

Image source not binding in xamarin form

My object has a property set elsewhere called:
ImageSrc = "icon.png"
In xaml file I am trying to bind to this:
<Image Source="{Binding ImageSrc}" />
In the ViewModel I am setting the property as such:
private string _imageSrc;
public string ImageSrc
{
get => this._imageSrc;
set
{
this._imageSrc = value;
this.RaisePropertyChanged(() => this.ImageSrc);
}
}
public override Task InitializeAsync(object _params)
{
ObjectParameters _objectParameters = (ObjectParameters)_params;
this.ImageSrc = _objectParameters.ImageSrc;
return Task.CompletedTask;
}
If I set this in class constructor:
this.ImageSrc = "icon.png";
it binds fine but not after calling:
public override Task InitializeAsync(object _params)
the property is set properly with the correct value of "icon.png" but the image does not show like when set in the class constructor. Any ideas appreciated.
I had to right click on Resources/drawable/icon.png then Add To Project and make sure Build Action: AndroidResource

Two questions about mvvm navigation of pages

I am trying to make a template-translator (.doc) from EN to other languages.
It is just for me.
I have already done simple mvvm navigation. For clear understanding what do I want, check picture:
First question: How do I translate ICommand from button "NextItem" to current selected page that has changed a item inside textBox, otherwise how do I Call Translate() method from current page for my Button which is in the MainView?
Second question: How do I put all pages that I have on the Window in the Combobox on the Upper side window, and select page from there, like I do this with my Buttons.
How it is now:
<Button
x:Name="ButtonSecondView"
Width="200"
Command="{Binding GoToSecondViewCommand}"
Content="SecondView" />
<Button
x:Name="ButtonNextItem"
Grid.Row="2"
Width="250"
Command="{Binding NextRandomItem}"
Content="Next item" />
MyCollection is just a stub which generates random items(1 item, 3 item, etc...).
There I can translate some parameters to page while it is initializing.
public MainViewModel()
{
MyCollection = new MyCollection();
CurrentViewModel = new FirstViewModel(this,MyCollection.GetRandomItem());
PageList = MyCollection.GetList();
}
public ICommand GoToFirstViewCommand
{
get
{
return new RelayCommand(() => { CurrentViewModel = new FirstViewModel(this, MyCollection.GetRandomItem()); });
}
}
public ICommand GoToSecondViewCommand
{
get
{
return new RelayCommand(() => { CurrentViewModel = new SecondViewModel(this, MyCollection.GetRandomItem()); });
}
}
ctor in SecondViewModel
public SecondViewModel(INotifyContentChanged contentChanged,string Parametrs)
{
ContentChanged = contentChanged;
TextContent = Parametrs;
}
One more time: First question.
I have many pages (in there 3), and I need to click the button on bottom, and in my page. In my current page I get text from textBox, and input these parameters to my method, like Translate(string field1). And this works on all pages that I want. If I change page in which I select Combobox items, I can do the same button click to button, and text from textBox inputted in my method Translate(string field1).
To navigate and pass the parameters to the corresponding page view models I stick to your pattern and used composition. I introduced a composition container that holds all page view models in a Dictionary<string, IPageViewModel>. Therefore all page view models must implement this interface. As the key I used the page view model's type name (e.g. nameof(FirstViewModel)). I also introduced a new property called PageNavigationParameter that binds to the TextBox in order to get the content (which is supposed to be passed to the corresponding page view model).
A second Dictionary<string, string> maps the display name of each page view model (the page name to be displayed in the ComboBox) to the actual page view model name (that matches the class name e.g. nameof(FistViewModel)). This way you can get the desired page view model by class name or if in the navigation scope from the page display name.
To select pages from a ComboBox you could do this:
create a collection of page names in the view model and bind it to the ComboBox.ItemSource
bind the ComboBox.SelectedItem property to the view model
navigate to page when the view model's property changed
To make this example work you need a common interface that all page view models must implement (e.g. class FirstViewModel : IPageViewModel). This interface must contain at least the PageNavigationParameter
The page view model interface
interface IPageViewModel
{
string PageNavigationParameter { get; set; }
}
Main view model (using composition)
class MainViewModel
{
public MainViewModel()
{
// The Dictionary to get the page view model name
// that maps to a page display name
this.PageViewModelNameMap = new Dictionary<string, string>()
{
{"First Page", nameof(FirstViewModel)},
{"Second Page", nameof(SecondViewModel)}
};
// The ComboBox's items source
// that holds the page view model display names
this.PageNames = new ObservableCollection<string>(this.PageViewModelNameMap.Keys);
// The Dictionary that stores all page view models
// that can be retrieved by the page view model type name
this.PageViewModels = new Dictionary<string, IPageViewModel>()
{
{nameof(FirstViewModel), new FirstViewModel()},
{nameof(SecondViewModel), new SecondViewModel()}
};
this.CurrentPageViewModel = this.PageViewModels[nameof(FirstViewModel)];
this.PageNavigationParameter = string.Empty;
}
// You can use this method as execute handler
// for your NavigateToPage command too
private void NavigateToPage(object parameter)
{
if (!(parameter is string pageName))
{
return;
}
if (this.PageViewModelNameMap.TryGetValue(pageName, out string pageViewModelName)
{
if (this.PageViewModels.TryGetValue(pageViewModelName, out IPageViewModel pageViewModel)
{
pageViewModel.PageNavigationParameter = this.PageNavigationParameter;
this CurrentPageViewModel = pageViewModel;
}
}
}
private bool CanExecuteNavigation(object parameter) => parameter is string destinationPageName && this.PageViewModelNameMap.Contains(destinationPageName);
private void OnSelectedPageChanged(string selectedPageName)
{
NavigateToPage(selectedPageName);
}
private ObservableCollection<string> pageNames;
public ObservableCollection<string> PageNames
{
get => this.pageNames;
set
{
this.pageNames = value;
OnPropertyChanged();
}
}
private string selectedPageName;
public string SelectedPageName
{
get => this.selectedPageName;
set
{
this.selectedPageName = value;
OnPropertyChanged();
OnSelectedPageChanged(value);
}
}
private string pageNavigationParameter;
public string PageNavigationParameter
{
get => this.pageNavigationParameter;
set
{
this.pageNavigationParameter= value;
OnPropertyChanged();
}
}
private Dictionary<string, ViewModelBase> pageViewModels;
public Dictionary<string, ViewModelBase> PageViewModels
{
get => this.pageViewModels;
set
{
this.pageViewModels = value;
OnPropertyChanged();
}
}
private Dictionary<string, string> pageViewModelNameMap;
public Dictionary<string, string> PageViewModelNameMap
{
get => this.pageViewModelNameMap;
set
{
this.pageViewModelNameMap = value;
OnPropertyChanged();
}
}
private IPageViewModel currentPageViewModel;
public IPageViewModel CurrentPageViewModel
{
get => this.currentPageViewModel;
set
{
this.currentPageViewModel= value;
OnPropertyChanged();
}
}
}
The controls that have a cross page scope must have the MainViewModel as their DataContext.
XAML snippet
<!-- The page menu (DataContext is MainViewModel) -->
<ComboBox SelectedItem="{Binding SelectedPageName}" ItemsSource="{Binding PageNames}" />
<!-- The navigation parameter TextBox (DataContext is MainViewModel) -->
<TextBox Text="{Binding PageNavigationParameter}" />
For your navigation button commands you can use the same MainViewModel.NavigateToPage() method as the execute delegate handler and CanExecuteNavigation as the can execute handler. So you now have a single navigation command (e.g. NavigateToPage) that navigates to the destination page by passing the page display name as CommandParameter.

Register changes in the ViewModel on Entity modifications

I have an observableCollection containing several viewModels which are bound to an entity model each. The viewModel additionally contains several calculated text values:
public class SampleViewModel : NotificationObject
{
private Entity _myModel;
public Entity Model
{
get;
private set;
}
public string HasEntries
{
get
{
if(Model.Entries.Count > 0)
return "Model has Entries";
else
return "Model has no Entries";
}
}
How can i now inform the ViewModel and the ObservableCollection in the View that the HasEntries-Property has changed when the model gets updated?
sampleViewModel.Model.Entries.Add(entry);
Edit:
To clarify: I sometimes add an entry to the model by just setting an reference in the entry-entity:
private void addEntry(){
Entry t = new Entry();
t.IDModel = sampleViewModel.Model.ID;
dataAccessLayer.AddEntry(t);
}
All of this happens in the same context and so the object will show up in the sampleViewModel. I just have to find a way to catch this event and notify the viewModel about the newly added object.
Instead of exposing your model directly, why not just create a method that adds entries and notifys of the change all in one.
public class SampleViewModel : NotificationObject
{
private Entity Model {get;set;}
public string HasEntries
{
get
{
if(Model.Entries.Count > 0)
return "Model has Entries";
else
return "Model has no Entries";
}
}
public void AddEntry(Entry entry)
{
Model.Entries.Add(entry);
//Execute you nofity property changed
NotifyPropertyChanged("HasEntries");
}
}
Then
sampleViewModel.AddEntry(entry);
I found a pretty easy solution. As it turns out every entity automatically raises a propertyChanged-Event when a property is changed. All i had to do was to bind the PropertyChanged-Event of the Model to the viewModel:
Model.PropertyChanged += Model_PropertyChanged;
And in my specific case because it is a collection:
Model.Entries.AssociationChanged += ModelEntries_PropertyChanged;
protected void ModelEntries_PropertyChanged(object sender, CollectionChangedEventArgs)
{
RaisePropertyChanged(() => this.HasEntries);
}

Re-assigning a new model in MVVM -> does not update child collection view models

I have a root Model ~ PropertyPortfolio ~ which I save/load from an XML file. It contains a child collection of Property objects.
When I load the application I load this object into a PropertyPortfolioService. The model uses INotifyPropertyChanged, so does the service - so I've got that covered.
I have a child view which displays a child collection of Property objects in a grid. Binding works fine.
The problem I have is this:
When I open a new PropertyPortfolio from a file I re-assign the PropertyPortfolio object in the service:
this.PropertyPortfolio = loadedPropertyPortfolio;
The child view/viewmodel does not update.
The solution I currently have is to load the new portfolio and recreate child objects, like this:
PropertyPortfolio loadedPropertyPortfolio = /* Code to load new portfolio from XML */;
this.PropertyPortfolio.Properties.Clear();
foreach (var property in loadedPropertyPortfolio.Properties)
{
this.PropertyPortfolio.Properties.Add(property);
}
I'm looking for a better solution.
I hope that's descriptive enough?
Further information - The problem is with the PropertyViewModel (which contains a Property model object plus IsSelected logic).
This is the VM for an individual Property (for each Property in the collection):
public class PropertyViewModel
{
public Property Property
{
get { return this.property; }
}
public bool IsSelected
{
get { return this.isSelected; }
set
{
SetProperty(ref this.isSelected, value, () => IsSelected);
}
}
public PropertyViewModel(Property property)
{
this.Property = property;
}
// Other code (fields etc)
}
This is the view model for the Properties view (containing the grid):
public class PropertiesViewModel : ViewModelBase
{
public ObservableCollection<PropertyViewModel> PropertyVMs
{
get { return this.propertyVMs; }
}
public PropertiesViewModel(PropertyPortfolio propertyPortfolio)
{
Func<Property, PropertyViewModel> viewModelCreator = model => new PropertyViewModel(model);
this.propertyVMs = new ObservableViewModelCollection<PropertyViewModel, Property>(propertyPortfolio.Properties, viewModelCreator);
}
// Other code (fields etc)
}

How to save and retrieve checkhbox value from database in mvvm model?

I want to save and retrieve a check box value from database in mvvm model , I am using this below code .
checkbox Xaml:
<CheckBox x:Name="CbxAccess"
Margin="380,50,0,180"
FontSize="14"
IsChecked="{Binding IsActive, Mode=TwoWay}"
Checked="cbxhasAccess_Checked_1"
Unchecked="cbxhasAccess_Checked_1"
HorizontalAlignment="Left"
Width="20">
</CheckBox>
checkbox Xaml.cs :
private void cbxhasAccess_Checked_1(object sender, RoutedEventArgs e)
{
var rbtn = sender as CheckBox;
var settingsmodel = new SettingsModel();
if (rbtn.IsFocused)
{
if ((bool)rbtn.IsChecked)
{
settingsmodel.IsActive = true;
}
else
{
settingsmodel.IsActive = false;
}
}
}
model :
private bool isActive;
public bool IsActive
{
get
{
return isActive;
}
set
{
isActive = value;
RaisePropertyChanged("IsActive");
}
}
viewmodel :
SettingsModel st = new SettingsModel();
var createconfigureBatchJobsXElement = new XElement("UpgradeAccessSettings");
createconfigureBatchJobsXElement.Add(new XElement("IsActive", st.IsActive));
root.Add(createconfigureBatchJobsXElement);
in above viewmodel i am trying to get the active status and save the xelement to database , currently i am unable get the status properly , though it is checked i am getting false .
I want to retrive the status as well from database and show that in UI and do some other operations in the application based on the status(my intention is to save the value and do some operations in application based on true or false ).
please help me , thanks in adavnce
There are few problems with your code, namely:
you're creating way too many SettingsModel instances (should have one, living in view model bound to your view)
cbxhasAccess_Checked_1 are not needed as you're binding with TwoWay mode
To fix this, first and foremost you should expose settings model (or IsActive property) on your view model:
// view model
public SettingsModel Settings { get; private set; }
// view model constructor
Settings = new SettingsModel();
Then in your view your binding changes to:
<CheckBox ... IsChecked="{Binding Settings.IsActive, Mode=TwoWay}" />
Note that cbxhasAccess_Checked_1 method is not needed.
This however (exposing settings model) is not the best idiomatic way to resolve this problem with MVVM. Instead, you could keep SettingsModel private within view model and wrap around it's IsActive property:
public bool IsActive
{
get { return settingsModel.IsActive; }
set
{
if (settingsModel.IsActive != value)
{
settingsModel.IsActive
RaisePropertyChanged("IsActive");
}
}
}
Either way, important point is to have only one instance of SettingsModel within view model.

Categories