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}"/>
Related
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.
SO Community, please bear with me as I am a newbie and I'm still learning(slowly). I have tried to implement absolutely every potential solution on the net with no success. I think I've completely confused myself with DependencyMethods, DependencyProperties, RelayCommands, ICommand, BaseViewModels, etc........
Here's my scenario... I have a MainWindow(MainWindow) with a Frame containing a UserControl(SampleUC). The MainWindow and UserControl DataContexts are pointed to their respective ViewModels(MainWindowVM, SampleUCVM). MainWindowVM and SampleUCVM are children of a Base ViewModel(BASEVM) which uses INotifyPropertyChanged through an ObservableCollection helper class. There is a Combobox inside SampleUC that holds the Facilities ObservableCollection constructed in SampleUCVM with method "fillFacility" and stores the SelectedFacilityNum when selected in the Combobox with the "GetFacilityNum()" method.
I would like to pull out the methods "fillFacility" and "GetFacilityNum" from the SampleUCVM and place them either in the BASEVM or a separate class where they can be accessed and used by other ViewModels. They will be used repeatedly throughout my project. Would someone be able to explain to me how to do this? Thank you for all your help and patience!
SampleUC.xaml
<Grid>
<Label Content="Facility" HorizontalAlignment="Left" Margin="10,32,0,0" VerticalAlignment="Top" Width="87" Height="27"/>
<ComboBox Name="cboFacilities"
HorizontalAlignment="Left" Margin="119,37,0,0" VerticalAlignment="Top" Width="120"
DisplayMemberPath="FacilityName"
SelectedValuePath="FacilityName"
ItemsSource="{Binding Facilities}"
SelectedValue="{Binding SelectedFacility, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
/>
</Grid>
SampleUCVM
public class SampleUCVM : BASEVM
{
#region MySQL Connection
const string dbConnectionString = #"datasource=localhost;port=3306;Initial Catalog='optest1a1';username=root;password=";
#endregion
private ObservableCollection<Facilities> _facilitiesList;
private string _selectedFacility;
private int _selectedFacilityNum;
public ObservableCollection<Facilities> Facilities
{
get { return _facilitiesList; }
set
{
SetProperty(ref _facilitiesList, value, () => Facilities);
}
}
public string SelectedFacility
{
get { return _selectedFacility; }
set
{
SetProperty(ref _selectedFacility, value, () => SelectedFacility);
if (_selectedFacility != null)
{
GetFacilityNum();
}
}
}
public int SelectedFacilityNum
{
get { return _selectedFacilityNum; }
set { SetProperty(ref _selectedFacilityNum, value, () => SelectedFacilityNum); }
}
public SampleUCVM()
{
Facilities = new ObservableCollection<Facilities>();
fillFacilities();
}
private void fillFacilities()
{
using (MySqlConnection con = new MySqlConnection(dbConnectionString))
{
Facilities = new ObservableCollection<Facilities>();
con.Open();
string Query = "SELECT * FROM facilities";
MySqlCommand createCommand = new MySqlCommand(Query, con);
MySqlDataReader dr = createCommand.ExecuteReader();
int count = 1;
while (dr.Read())
{
string FacilityName = dr.GetString(1);
Facilities facilityname = new Facilities(count, FacilityName);
Facilities.Add(facilityname);
count++;
}
con.Close();
}
}
private void GetFacilityNum()
{
if (SelectedFacility != null)
{
using (MySqlConnection con = new MySqlConnection(dbConnectionString))
{
con.Open();
string Query = "SELECT Facility_ID_Num FROM facilities WHERE Facility_Name='" + SelectedFacility + "' ";
MySqlCommand createCommand = new MySqlCommand(Query, con);
MySqlDataReader dr = createCommand.ExecuteReader();
int count = 1;
while (dr.Read())
{
int FacilityNum = dr.GetInt32(0);
SelectedFacilityNum = FacilityNum;
count++;
}
con.Close();
}
}
}
}
BASEVM
public class BASEVM : ObservableObject
{
public BASEVM()
{
}
}
I would look into implementing a data access layer for this kind of thing. For example:
public class DataAccess
{
public ObservableCollection<Facilities> GetFacilities()
{
ObservableCollection<Facilities> facilities = new ObservableCollection<Facilities>();
using (MySqlConnection con = new MySqlConnection(dbConnectionString))
{
con.Open();
string Query = "SELECT * FROM facilities";
MySqlCommand createCommand = new MySqlCommand(Query, con);
MySqlDataReader dr = createCommand.ExecuteReader();
int count = 1;
while (dr.Read())
{
string FacilityName = dr.GetString(1);
facilities facilityname = new Facilities(count, FacilityName);
facilities.Add(facilityname);
count++;
}
con.Close();
}
return facilities;
}
}
You could then include an instance of this on your ViewModel classes (or on some ViewModel base class).
With regards to your GetFacilityNum() method, this could also go into the DataAccess class, but personally I think a better solution would be to include the Facility_ID_Num as a property of your Facilities class, if possible. You would then just need to amend the GetFacilities() method to pull that in as well, but then within the application you'd always be able to access this without any database calls as it would be part of your Facilities Model.
You're making things much complex than they really are.
ObservableCollection<T> is sufficient to notify WPF controls on changes has happend to it so far; just make sure it is properly bound from XAML.
As such
public ObservableCollection<Facilities> Facilities
{
get { return _facilitiesList; }
set
{
SetProperty(ref _facilitiesList, value, () => Facilities);
}
}
is redundant and should be simplified to
public ObservableCollection<Facilities> Facilities
{
get;
}
and initialized only once - from the constructor.
As far as you want it to be at the base view model, just move the code there. Then inherit your child models from your base model and thus child would get such a property as part of themselves.
And forget about SetValue(...) unless you register your own, custom DependencyProperty which is necessary only for advanced scenarios.
Those are start points.
Have fun.
I agree with Peter Duniho your question is too broad, and there is no definite answer to it, but I would like to highlight some points to you.
What is the purpose of your BASEVM And why it inherits from ObservableObject
Class that wraps an object, so that other classes can notify for
Change events. Typically, this class is set as a Dependency Property
on DependencyObjects, and allows other classes to observe any changes
in the Value.
If you want a basic notification change functionality you could do something like this.
public class ViewModelBase:INotifyPropertyChanged{
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName]string propertyName = null)
{
OnPropertyChanged(new PropertyChangedEventArgs(propertyName));
}
protected virtual void OnPropertyChanged(PropertyChangedEventArgs args)
{
PropertyChanged?.Invoke(this, args);
}
protected void RaisePropertyChanged([CallerMemberName]string propertyName = null)
{
OnPropertyChanged(propertyName);
}
protected virtual bool SetProperty<T>(ref T storage, T value, [CallerMemberName] string propertyName = null)
{
if (Equals(storage, value)) return false;
storage = value;
RaisePropertyChanged(propertyName);
return true;
}
}
source
After that, you inherit from this ViewModelBase class. And when any property changed, you fire the PropertyChanged event like this:
public int SelectedFacilityNum
{
get { return _selectedFacilityNum; }
set { SetProperty(ref _selectedFacilityNum,value); }
}
I would like to pull out the methods "fillFacility" and
"GetFacilityNum" from the SampleUCVM and place them either in the
BASEVM or a separate class where they can be accessed and used by
other ViewModels
fillFacility method, as shown in your code is dealing with the database. As far as I know, in MVVM pattern, ViewModels are meant to meditate between the View and the Model
The view model acts as an intermediary between the view and the model,
and is responsible for handling the view logic. Typically, the view
model interacts with the model by invoking methods in the model
classes. The view model then provides data from the model in a form
that the view can easily use. The view model retrieves data from the
model and then makes the data available to the view, and may reformat
the data in some way that makes it simpler for the view to handle.
So, I suggest to separate the Data Access logic from your view model to a separate Data Access layer. For example, you can use the Repository pattern and create a FacilityRepository. And then you can provide this repository to the ViewModels require this functionality. And if you want to be more strict you can create IFacilityRepository interface, make the FacilityRepository implement it, and then you inject this interface to any ViewModel requires this functionality.
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 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 ;)
Objective-c/cocoa offers a form of binding where a control's properties (ie text in a textbox) can be bound to the property of an object. I am trying to duplicate this functionality in C# w/ .Net 3.5.
I have created the following very simple class in the file MyClass.cs:
class MyClass
{
private string myName;
public string MyName
{
get
{
return myName;
}
set
{
myName = value;
}
}
public MyClass()
{
myName = "Allen";
}
}
I also created a simple form with 1 textbox and 1 button. I init'd one instance of Myclass inside the form code and built the project. Using the DataSource Wizard in Vs2008, I selected to create a data source based on object, and selected the MyClass assembly. This created a datasource entity. I changed the databinding of the textbox to this datasource; however, the expected result (that the textbox's contents would be "allen") was not achieved. Further, putting text into the textbox is not updating the name property of the object.
I know i'm missing something fundamental here. At some point i should have to tie my instance of the MyClass class that i initialized inside the form code to the textbox, but that hasn't occurred. Everything i've looked at online seems to gloss over using DataBinding with an object (or i'm missing the mark entirely), so any help is great appreciated.
Edit:
Utilizing what I learned by the answers, I looked at the code generated by Visual Studio, it had the following:
this.myClassBindingSource.DataSource = typeof(BindingTest.MyClass);
if I comment that out and substitute:
this.myClassBindingSource.DataSource = new MyClass();
I get the expected behavior. Why is the default code generated by VS like it is? Assuming this is more correct than the method that works, how should I modify my code to work within the bounds of what VS generated?
You must assign the textbox's data source to be your new datasource. But additionally, you must assign the datasource's datasource to be an instance of your class.
MyDataSource.DataSource = new MyClass();
TextBox1.DataSource = MyDataSource;
That should work for your first pass. As others have mentioned, you may need to implement additional interfaces on your class (INotifyPropertyChanged etc), if you are going to be modifying the class properties via any background processes.
If you are only updating the properties via the form, then you do not need this step.
You should implement the INotifyPropertyChanged interface to your MyClass type:
public class MyClass : INotifyPropertyChanged
{
private string _myName;
public string MyName
{
get { return _myName; }
set
{
if( _myName != value )
{
_myName = value;
OnPropertyChanged("MyName");
}
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string propertyName)
{
if( PropertyChanged != null )
PropertyChanged( this , new PropertyChangedEventArgs(propertyName) );
}
}
This interface is required for the DataBinding infrastructure if you want to support simple databinding.
The INotifyPropertyChanged interface is used to notify a 'binding' that a property has changed, so the DataBinding infrastructure can act accordingly to it.
Then, you can databind the MyName property to the Text Property of the textbox.
I get an error message in the DataBinding.Add("TEXT", myObject, myObjectProperty) method
This is probably because you're missing the explicit {get;set;} on the property declaration!
BAD:
public string FirstName; //<-- you will not be able to bind to this property!
GOOD:
public string FirstName { get; set; }
Looks like you probably need a Bindable attribute on your MyName property (and follow Frederik's suggestion as well):
[System.ComponentModel.Bindable(true)]
public string MyName
{
get { return _myName; }
set
{
if( _myName != value )
{
_myName = value;
OnPropertyChanged("MyName");
}
}
}
Via: http://support.microsoft.com/kb/327413
I don't have any code in front of me, but I think the data source is kind of like a collection. You have to add an instance of MyClass to the data source, and that's what the form fields will bind to. There's also methods for navigating through the data source to multiple instances of MyClass, but it doesn't sound like you need that. Check the docs for DataSource.
I don't think you need to implement any fancy interfaces. I seem to remember there's a method on the data source that lets you refresh or rebind the current item after you change some values.
using System.Collections.Generic;
public class SiteDataItem
{
private string _text;
private string _url;
private int _id;
private int _parentId;
public string Text
{
get
{
return _text;
}
set
{
_text = value;
}
}
public string Url
{
get
{
return _url;
}
set
{
_url = value;
}
}
public int ID
{
get
{
return _id;
}
set
{
_id = value;
}
}
public int ParentID
{
get
{
return _parentId;
}
set
{
_parentId = value;
}
}
public SiteDataItem(int id, int parentId, string text, string url)
{
_id = id;
_parentId = parentId;
_text = text;
_url = url;
}
public static List<SiteDataItem> GetSiteData()
{
List<SiteDataItem> siteData = new List<SiteDataItem>();
siteData.Add(new SiteDataItem(1, 0, "All Sites", ""));
siteData.Add(new SiteDataItem(2, 1, "Search Engines", ""));
siteData.Add(new SiteDataItem(3, 1, "News Sites", ""));
siteData.Add(new SiteDataItem(4, 2, "Yahoo", "http://www.yahoo.com"));
siteData.Add(new SiteDataItem(5, 2, "MSN", "http://www.msn.com"));
siteData.Add(new SiteDataItem(6, 2, "Google", "http://www.google.com"));
siteData.Add(new SiteDataItem(7, 3, "CNN", "http://www.cnn.com"));
siteData.Add(new SiteDataItem(8, 3, "BBC", "http://www.bbc.co.uk"));
siteData.Add(new SiteDataItem(9, 3, "FOX", "http://www.foxnews.com"));
return siteData;
}
}
More detail you can read tutorial dapfor. com