I just started to do MVVM because a have heard a lot of its benefits about:
Clean code
Reusable code
Better code organized
So started to do a usual "Code behind" to make sure that my code is working, and then apply MVVM to clean it up.
so here is one of my "code behind" example:
private const int LED_PIN = 17, RELAY_PIN = 27;
private GpioPin LEDpin, RELAYpin;
private bool InitGPIO(TextBlock txt)
{
var gpio = GpioController.GetDefault();
// Show an error if there is no GPIO controller
if (gpio == null)
{
txt.Text = "There is no GPIO controller on this device.";
txt.Foreground = new SolidColorBrush(Colors.Red);
return false;
}
txt.Text = "GPIO controller initialized correctly.";
txt.Foreground = new SolidColorBrush(Colors.Green);
LEDpin = gpio.OpenPin(LED_PIN);
RELAYpin = gpio.OpenPin(RELAY_PIN);
LEDpinValue = GpioPinValue.Low;
RELAYpinValue = GpioPinValue.High;
LEDpin.Write(LEDpinValue);
RELAYpin.Write(RELAYpinValue);
LEDpin.SetDriveMode(GpioPinDriveMode.Output);
RELAYpin.SetDriveMode(GpioPinDriveMode.Output);
return true;
}
To use this "InitGPIO" method I have to provide:
fixed "int" pin.
GpioPin types.
GpioPinValue type.
A textblock to display what is wrong.
I already create a ViewModelBase and the simplify method to get it read:
public class ViewModelBase
{
public InitGpioCommand InitGpiocommand { get; set; }
public ViewModelBase()
{
this.InitGpiocommand = new InitGpioCommand(this);
}
public bool InitGPIO(DigitalControl dc)
{
var gpio = GpioController.GetDefault();
// Show an error if there is no GPIO controller
if (gpio == null)
{
dc.Status.Text = "There is no GPIO controller on this device.";
dc.Status.Foreground = new SolidColorBrush(Colors.Red);
return false;
}
dc.Status.Text = "GPIO controller initialized correctly.";
dc.Status.Foreground = new SolidColorBrush(Colors.Green);
dc.DevicePin = gpio.OpenPin(dc.PinNumber);
dc.PinValue = GpioPinValue.Low;
dc.DevicePin.Write(dc.PinValue);
dc.DevicePin.SetDriveMode(GpioPinDriveMode.Output);
return true;
}
}
everything had been wrap up in this Model:
public class DigitalControl
{
public TextBlock Status { get; set; }
public GpioPin DevicePin { get; set; }
public GpioPinValue PinValue { get; set; }
public int PinNumber { get; set; }
}
and my current button to fire the InitGPIO method:
<Button x:Name="FirstLightTest"
Content="Test"
Command="{Binding InitGPIO,Source={StaticResource viewmodel}}">
</Button>
Of course this will not work.
I realize I have to:
Pass a "TextBlock" from xaml to the method parameter in ViewModelBase.
Assign GpioPin, GpioPinValue and PinNumber somewhere in C# code and also pass them to the Method inside the ViewModelBase.
In order to fill up all the parameters in that method.
I don't really know this is a bad MVVM design or not but I think the best to use this complicated pattern is to break it down smaller problems like this and see if how valuable it is.
There are some mix-ups in the code. First of all, the Binding is bound to InitGIPO, which is a method. This will not work as Command can be bound to a ICommand property only. In this case, the proper target would be the InitGpiocommand. I always recommend using an existing MVVM framework as a starting point, for example the MVVM Light framework or MvvmCross. Those provide the implementation of a simple DelegateCommand which can just call a simple parameterless method. You could then have the method look like this:
private bool InitGPIO()
{
...
}
Now, where do we get the DigitalControl instance? We will add a property to our view model that will represent this instance:
public DigitalControl DigitalControl { get; } = new DigitalControl();
As #max already mentioned, it definitely discouraged to put UI controls into any View Model properties, so let's change the definition of DigitalControl to this:
public class DigitalControl
{
public string Status { get; set; }
public GpioPin DevicePin { get; set; }
public GpioPinValue PinValue { get; set; }
public int PinNumber { get; set; }
}
The Status property can now be data-bound to the TextBlock, like this:
<TextBlock Text="{Binding DigitalControl.Status, Mode=TwoWay}" .../>
Once the user modifies the TextBlock, the change will automatically propagate to the view model's DigitalControl instance's Status property.
To reference the DigitalControl property in InitGIPO you can just reference it as any other property within the view model.
To me having a UI component (TextBlock) inside a model is a violation of MVVM-pattern. Instead the TextBlock's properties should be bound to the view-model via binding mechanism and not assigned directly.
To sum up:
Remove the TextBlock reference from the model
Provide properties in the view-model that TextBlock can be bound to (text, foreground color or a boolean flag).
Bind to the view-model properties from xaml.
Related
I know, title is a little confusing so let me explain. I have a user control that has a dependency property. I access this dependency property with a regular property called Input. In my view model I also have a property called Input. I have these two properties bound together in XAML using two-way binding as shown below:
<uc:rdtDisplay x:Name="rdtDisplay" Input="{Binding Input, Mode=TwoWay}" Line1="{Binding myRdt.Line1}" Line2="{Binding myRdt.Line2}" Height="175" Width="99" Canvas.Left="627" Canvas.Top="10"/>
Okay in my view model, I call a method whenever the value of Input is changed as shown in my property:
public string Input
{
get
{
return input;
}
set
{
input = value;
InputChanged();
}
}
The problem with this is that when I set the value of Input in my view model it only updates the value of the variable input as per my setter in my property. How can I get this to update back to the dependency property in the user control? If I leave the code input = value; out then I get a compilation error.
I need something like this:
public string Input
{
get
{
return UserControl.Input;
}
set
{
UserControl.Input = value;
InputChanged();
}
}
If I make the Input property in my view model look like this:
public string Input
{
get; set;
}
then it works, however, I am unable to call the InputChanged() method that I need to call when the Property is changed. All suggestions are appreciated.
Implement INotifyPropertyChanged in your ViewModel
public class Sample : INotifyPropertyChanged
{
private string input = string.Empty;
public string Input
{
get
{
return input;
}
set
{
input = value;
NotifyPropertyChanged("Input");
InputChanged();
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(String info)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(info));
}
}
}
In your case, you can do it in the code behind of your usercontrol
I have a DataGridView bound to a list of custom objects. A column is bound to a double datatype, and when I try to empty its content (to null), it throws a data error indicating DbNull cannot be converted into Double. What I'd like to accomplish is when a user enters a null value for the databound column, I'd like to set it to a default generic value, say 3.0.
I can handle dataerror and replace the value there, but this seems like a hackish solution. What is the recommended way of handling this?
Edit:
Here's my data class. The double datatype mentioned above is USL.
public class SPCModelDTO
{
public string ProcessName { get; set; }
public string CustomerName { get; set; }
public string ModelName { get; set; }
public double USL { get; set; }
}
Here's my code for databinding
dgvModel.DataSource = new BindingList<SPCModelDTO>(modelList);
Matching property name is declared in designer w/ respect to the dataclass properties.
As a side note, I cannot change USL's datatype to a nullable double.
Create a ViewModel which will be bound to the DataGrdiView.
In the ViewModel you can wrap USL in the nullable type
public class SPCModelDTOViewModel: INotifyPropertyChanged
{
private SPCModelDTO _Model;
public double? USL
{
get { return _Model.USL;}
set
{
if(value.HasValue == false)
_Model.USL = 3.0;
else
_Model.USL = value;
this.RaisePropertyChangedEvent();
}
}
//Other model's properties
}
Line this.RaisePropertyChangedEvent(); will notify View(DataGridView) about changes and empty(null) value will be changed to default one
Of course your ViewModel class must implement INotifyPropertyChanged
How to: Implement the INotifyPropertyChanged Interface
for a databound column you can set DataSourceNullValue in DatagridViewColumn.DefaultCellStyle
dgvModel.DataSource = data;
var col = dgvModel.Columns["USL"];
col.DefaultCellStyle.DataSourceNullValue = 3;
As the ViewModel has the job to "prepare" the Model's properties to get displayed in the View, what is the best way of referring to the underlying Models properties from the ViewModel?
I could think about two solutions by now:
Option 1 - Duplicate the Model's properties in the ViewModel (wrapper-approach)
Architecture
class Model
{
public string p1 { get; set; }
public int p2 { get; set; }
}
class ViewModel : INotifyPropertyChanged
{
// Model-instance for this ViewModel
private Model M;
public string p1
{
get { return M.p1; }
set
{
M.p1 = value;
// assuming View controls are bound to the ViewModel's properties
RaisePropertyChanged("p1");
}
}
// let's say, I only want to check a Checkbox in the View,
// if the value of p2 exceeds 10.
// Raising the property changed notification would get handled
// in the modifying code instead of the missing setter of this property.
public bool p2
{
get
{
if (M.p2 > 10)
{ return true; }
else
{ return false; }
}
}
// Initialize the Model of the ViewModel instance in its c'tor
public ViewModel()
{ M = new Model(); }
}
Binding
<Textbox Text="{Binding p1}"/>
<Checkbox IsEnabled="False" IsChecked="{Binding p2, Mode=OneWay}"/>
Advantages
Full control about how the Model's properties are displayed on the View as shown in p2: int gets converted to bool on demand.
Changes of the properties of the ViewModel could be raised individual, might be a little performance increase compared to option 2.
Disadvantages
Violation of DRY.
More Code to write/maintain.
Modifications to the Model/ViewModel could easily become shotgun surgery.
Option 2 - Treat the whole Model as property of the ViewModel
Architecture
class Model
{
public string p1 { get; set; }
public int p2 { get; set; }
}
class ViewModel : INotifyPropertyChanged
{
// Model instance for this ViewModel (private field with public property)
private Model _M;
public Model M
{
get { return _M; }
set
{
_M = value;
// Raising the changing notification for the WHOLE Model-instance.
// This should cause ALL bound View-controls to update their values,
// even if only a single property actually got changed
RaisePropertyChanged("M");
}
}
// Initialize the Model of the ViewModel instance in its ctor
public ViewModel()
{ M = new Model(); }
}
Binding
<Textbox Text="{Binding M.p1}"/>
<Checkbox IsEnabled="False" IsChecked="{Binding M.p2, Mode=OneWay, Converter={StaticResource InverseBooleanConverter}"/>
Advantages
Can save a lot of code.
Reduces complexity.
Increases maintainability.
Disadvantages
In this approach, the ViewModel is nothing more than a continuous-flow water heater for the Models properties, except for some possible interaction logic for the View.
No control about how the Model's properties are displayed in the View - which ultimately leads to total needlessness of the ViewModel and implementation of conversion logic in the View.
It is the responsibility of your ViewModel to expose the Model to the View, you should not expose the Model's properties as additional properties in the ViewModel, instead, your View should bind directly to the model.
Additionally, it isn't wrong to have logic in your Model, in fact, it makes more sense to contain model related code within the model, as opposed to the ViewModel.
Here is an example:
public class Movie
{
private string _Name;
public string Name
{
get { return _Name; }
set
{
_Name = value;
//Notify property changed stuff (if required)
//This is obviously a stupid example, but the idea
//is to contain model related logic inside the model.
//It makes more sense inside the model.
MyFavourite = value == "My Movie";
}
}
private bool _MyFavourite;
public bool MyFavourite
{
get { return _MyFavourite; }
set
{
_MyFavourite = value;
//Notify property changed stuff.
}
}
}
So to answer your question a little more directly, you should expose your model in the view model as a property.
public class ViewModel
{
private Movie _Model;
public Movie Model
{
get { return _Model; }
set
{
_Model = value;
//Property changed stuff (if required)
}
}
...
}
Therefore, your View will bind to the Model property, like you have already done so.
EDIT
In the example for casting to the type, you can implement a read-only property in your Model, like so:
public bool MyBool
{
get
{
return MyInt > 10; }
}
}
Now the magic here would be that you will need to call the INotifyPropertyChanged for this property whenever MyInt changes. So your other property would look something like this:
public int MyInt
{
get { ... }
set
{
_MyInt = value;
//Notify property changed for the read-only property too.
OnPropertyChanged();
OnPropertyChanged("MyBool");
}
}
In my view, Model should not have RaisePropertyChanged stuff. Some view models (e.g. Blazor) might not need it, others (e.g. WPF) might use other mechanisms like DependencyProperty. Thus, to me Model is a POCO class. Hence, it becomes ViewModel responsibility to report changes to the data up to the View. Consequently, ViewModel is bound to wrap Model's properties (OA's option 1).
You might want to look at AutoMapper to centralize the mappings.
I am new to WPF and C#. What is the best way to access the controls or objects (such as textboxes, buttons, etc) in another class. Below explains my situation. I am also using MEF if this makes a difference. Any help would be appreciated.
Thank You.
EsriMapView.xaml is where all of the objects are contained.
The class for this is EsriMapView.xaml.cs.
EsriMapViewModel.cs is the other class that I am trying to access EsriMapView.xaml from. The error I receive on all of the object is "The name blank does not exist in the current context."
Here is the code from the xaml class:
[Export]
[PartCreationPolicy(CreationPolicy.NonShared)]
public partial class EsriMapView : DialogWindowBase
{
//private int? initialZoom = null;
//private double? latitude = null;
//private double? longitude = null;
//private string json = string.Empty;
//private ObservableCollection<LocationPoint> marks = null;
//private bool isZoomToBounds = false;
//private string startPoint = string.Empty;
//private string endPoint = string.Empty;
//private string searchPoint = string.Empty;
public EsriMapView()
{
InitializeComponent();
}
[Import]
public EsriMapViewModel ViewModel
{
get
{
return this.DataContext as EsriMapViewModel;
}
set
{
this.DataContext = value;
}
}
}
}
I am also using MVVM. If you need any more information, just let me know. Thanks Again.
You shouldn't be trying to access your view from your view model. This breaks one of the tenets of MVVM, which makes testing your VMs difficult. Instead, your VM should expose properties to which the view binds. The VM then has access to the data it needs to do its job.
As a simple example, suppose your view model needs to know the current zoom level in order to perform some calculation. In your view model, you would have:
public double Zoom
{
get { return this.zoom; }
set
{
if (this.zoom != value)
{
this.zoom = value;
this.RaisePropertyChanged(() => this.Zoom);
}
}
}
private void DoSomeCalculation()
{
// can use this.zoom here
}
Then, in your view:
<Slider Value="{Binding Zoom}"/>
I have a Document object that looks like this:
public class Document
{
public Title { get; set; }
public Extension { get; set; }
public byte[] Data { get; set; }
}
The Extension is "pdf", "doc", "docx" and the like. This document is used for storing documents in a database (it's actually a DevExpress XPO object).
The problem I'm having is, I am binding a list of these objects to an imagelistbox, which has an associated image list of the icons to display for each file type. How can I set the image index on the imagelistbox item based on the Extension without storing the index in the domain object?
In WPF, I would have used the MVVM pattern to solve that issue : the XPO object wouldn't be directly used by the UI, instead a ViewModel object would expose the necessary properties so that they can easily be used in binding scenarios. MVVM is specific to WPF, but I believe the MVP pattern is very similar and can easily be used in Windows Forms. So, you could create a Presenter object which would act as an adapter between the UI and the XPO object :
public class DocumentPresenter
{
private Document _document;
public DocumentPresenter(Document document)
{
_document = document;
}
public string Title
{
get { return _document.Title; };
set { _document.Title = value; };
}
public string Extension
{
get { return _document.Extension; };
set { _document.Extension = value; };
}
public byte[] Data
{
get { return _document.Data; };
set { _document.Data = value; };
}
public int ImageIndex
{
get
{
// some logic to return the image index...
}
}
}
Now you just have to set the DataSource to a collection of DocumentPresenter objects, and set the ImageIndexMember to "ImageIndex"
Disclaimer : I never actually used the MVP pattern, only MVVM, so I might have got it wrong... anyway, you get the picture I guess ;)