I am rather new to MVVC/wpf, having mostly worked with winforms.
What I want to accomplish is dynamic databinding without using code behind in WPF. The user interface consists of a devexpress grid and a couple of buttons. Each button press loads an object list and presents the objects in the grid. The lists contain different object types depending on the button pressed. For this example I have two classes to present: FatCat and FatDog.
In winforms this works:
private void button1_Click(object sender, EventArgs e)
{
((GridView)gridCtrl.MainView).Columns.Clear();
gridCtrl.DataSource = new BindingSource(itsModel.GetAll<FatDog>(), null);
}
private void button2_Click(object sender, EventArgs e)
{
((GridView)gridCtrl.MainView).Columns.Clear();
gridCtrl.DataSource = new BindingSource(itsModel.GetAll<FatCat>(), null);
}
I have configured the grid to create columns dynamically, so everything just works. itsModel is of type CatClientModel.
In wpf I have defined the DataContext to be CatClientModel.
What should I use for ItemsSource in the grid to achieve the same behaviour as my winforms solution?
dxg:GridControl ItemsSource="{Binding SomeDynamicList}"
In other words, what should SomeDynamicList be in the code above? Or am I going about this the wrong way?
I am, as I stated, using the DevExpress wpf grid control, but the question ought to be general and apply to any control presenting object lists.
In other words, what should SomeDynamicList be in the code above?
SomeDynamicList should be an ObservableCollection<T> property to which you can add any objects of type T that you want to display in the GridControl.
Set the DataContext of the GridControl, or any of its parent elements, to an instance of a class where this property is defined:
public class CatClientModel
{
public ObservableCollection<Animal> SomeDynamicList { get; } = new ObservableCollection<Animal>();
}
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
DataContext = new CatClientModel();
}
}
Ok. But the thing is that the ObservableCollection contains different types. Unfortunately there is no feasible class to inherit from. I want to bind to either ObservableCollection or ObservableCollection depending on which button was pressed
Switch the DataContext then, or change the property into an IEnumerable and set it to a new collection each time the button is clicked. This requires you to implement the INotifyPropertyChanged interface in your view model
private System.Collections.IEnumerable _collection;
public System.Collections.IEnumerable MyProperty
{
get { return _collection; }
set { _collection = value; OnPropertyChanged(); }
}
If you want to use XAML to define which data sources your code maps to for each grid that is possible. That does require at least some method of MVVM manager either prism or mvvmlight to connect the view model to the view.
so if you do go the MVVM model route, the Model would contain a description for each of your grids like this:
public BulkObservableCollection<icd10facet> FacetList
{
get { return this._facets; }
set { SetProperty(ref this._facets, value); }
}
public INotifyTaskCompletion<BulkObservableCollection<PetsConvert>> ConceptList
{
get { return this._concept; }
set
{
SetProperty(ref this._concept, value);
}
}
In the XAML for your code the grid woud bind to the grid defined by ConceptList in this way:
ItemsSource="{Binding ConceptList.Result}"
this answer does NOT address how to wire up Prism 6.0 for example to use a view model but for examples see:
https://github.com/PrismLibrary/Prism
Which contains documentation and starter code. Keep in mind that there is not any specific reason that putting code in the code behind for the view is a problem, first solve the problem and then refactor if separation of concerns is an issue for you.
Using this technique you can bind each grid to its own data source. In the MVVM space buttons and other things use a commanding model to communicate with the view model.
<Button Content="Load Rule Data" Width="100" Height="40" HorizontalAlignment="Left" Margin="5px" Command="{Binding LoadRuleData }"/>
this requires defining a command delegate in the viewmodel for LoadRuleData
public DelegateCommand LoadRuleData { get; private set; }
and then (usually in the constructor) wire the DelegateCommand to the method that is going to do the work.
this.LoadRuleData = new DelegateCommand(this.loadRules);
Related
I'm working on a MVVM WPF application where i bind several values from ViewModel to View.
Now i have created a new ViewModel where i have to bind a value to a TextBox after a Button click. When i tried this simple binding it didn't work for me. To my surprise the binding works when the value is assigned in the constructor.
I'm confused.
ViewModel:
public ABCViewModel{
txtItems = "Hello world"; //this works
}
private string m_stxtItem = "";
public string txtItems
{
get { return this.m_stxtItem; }
set
{
if (this.m_stxtItem != value)
{
this.m_stxtItem = value;
}
}
}
public ICommand BindTextValue { get { return new RelayCommand(SeriesBinding); } }
private void SeriesBinding()
{
txtItems = "Hi"; //does not work
}
XAML:
<TextBox Text="{Binding txtItems,Source={StaticResource ABCViewModel}}" />
<Button Command="{Binding BindTextValue,Source={StaticResource ABCViewModel}}">Click</Button>
Why this didn't work and where am i wrong?
Simply answer: you are missing the INotifyPropertyChanged-implementation required for automatic data binding.
Extended answer to why it works when setting the value in the constructor:
the initial binding (reading of the value) from the view happens AFTER your ViewModel-constructor was called and your value was set
You need to implement the INotifyPropertyChanged interface on your ViewModel.
Here's some MSDN links on what the interface is and how to implement it:
INotifyPropertyChanged Interface
How to: Implement the INotifyPropertyChanged Interface
Basically because you've set the ViewModel as the DataSource for the View, upon construction, the View will look to the ViewModel for its values. From this point onwards the ViewModel needs some mechanism for notifying the View of changes. The View is not periodically updated or anything like that. This is where the INotifyPropertyChanged interface steps in. The WPF framework looks out for these events being fired and makes the View refresh its value.
I've been trying for ever to try and figure this out.
Story: I have one MainWindow and 2 User Controls.
When the MainWindow loads One control is visible and the other is not.
Once the user enters their data and settings, I need to make the other form visible.
The form that is invisible at startup needs to be initialized, because it is gathering data from the WMI of the computer it is running on. It is also gathering AD Information in preparation for the user.
For some reason I cannot get one form to show the other.
I think this is what I'm supposed to be looking at:
#region Class Variable
public string ShowSideBar { get { return (String)GetValue(VisibilityProperty); } set { SetValue(VisibilityProperty, value); }}
public DependencyProperty VisibilityProperty = DependencyProperty.Register("ShowSideBar", typeof(string), typeof(UserControl), null);
#endregion
This is set in my MainWindow Class, however, I have no idea why I cannot call it from any other usercontrol.
Is there any way to just expose something like this to all my forms from my MainWindow?
public int RowSpan {
get { return Grid.GetRowSpan(DockPanel1); }
set { Grid.SetRowSpan(DockPanel1,value); }
}
Dependency properties must be static. Why is the type string? Should it not be Visibility if you wish to bind the visibility of the controls to it?
Does it have to be a dependency property? You could just use a regular property as well and implement INotifyPropertyChanged, since you are not binding this field to anything, rather binding other things to it.
For a dependency property, try something like this instead:
public static readonly DependencyProperty SideBarVisibilityProperty = DependencyProperty.Register("SideBarVisibility", typeof(Visibility), typeof(MyTemplatedControl), null);
public Visibility SideBarVisibility
{
get { return (Visibility)GetValue(SideBarVisibilityProperty); }
set { SetValue(SideBarVisibilityProperty, value); }
}
Firstly, this application would benefit from application of the MVVM pattern.
However, without taking that approach, you can still resolve the problem you have. It would be unusual for a user control to rely on knowing what its parent is. The code behind for your main window would be the better place to put this code. (Not as good as a view model... but that's another story.)
Add to the control that should cause the side bar to be made visible an event, ShowSideBar. Attach a handler in the main window, and use the handler to display the second control. No need for dependency properties here at all.
public class MyControl : UserControl
{
...
public event EventHandler ShowSideBar;
// Call this method when you need to show the side bar.
public void OnShowSideBar()
{
var s = this.ShowSideBar;
if (s != null)
{
s(this, EventArgs.Empty);
}
}
}
public class MainWindow : Window
{
public MainWindow()
{
this.InitializeComponent();
this.FirstControl.ShowSideBar += (s, e) =>
{
this.SecondControl.Visibility = Visibility.Visible;
}
}
}
I fixed the initlized Component but changing.
X:Class="AdminTools.MainWindow.ShowSideBar" to x:Class="AdminTools.MainWindow".
now i have an issues where
<Window
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:User="clr-namespace:AdminTools.Controls.User"
xmlns:Custom="clr-namespace:AdminTools.Controls.Custom"
xmlns:Bindings="clr-namespace:AdminTools.Functions"
x:Class="AdminTools.MainWindow"
Title="MainWindow" Height="691.899" Width="1500"
>
<Window.DataContext>
<Bindings:ShowSideBar />
</Window.DataContext>
<Bindings:ShowSideBar /> = ShowSideBar does not exist in the namespace clr-namespace:AdminTools.Functions
ShowSideBar: member names cannot be the same as their enclosing type.
I have two options on different dialogs (classes) related to CheckBox in different XAML files:
First pair:
C#:
public class FirstClass : DependencyObject
{
public static readonly DependencyProperty testProperty =
DependencyProperty.Register("testProperty", typeof(bool),
typeof(FirstClass),
new UIPropertyMetadata(false));
public bool testProperty
{
get { return (bool)this.GetValue(testProperty); }
set { this.SetValue(testProperty, value); }
}
}
XAML:
<CheckBox IsChecked="{Binding Path=testProperty, Mode=TwoWay}">
Second pair:
C#
public class SecondClass : DependencyObject
{
public static readonly DependencyProperty testProperty =
FirstClass.testProperty.AddOwner(typeof(SecondClass));
public bool testProperty
{
get { return (bool)this.GetValue(testProperty); }
set { this.SetValue(testProperty, value); }
}
}
XAML:
<CheckBox IsChecked="{Binding Path=testProperty, Mode=TwoWay}">
I want to bind the option from the first dialog to the option from the second dialog (A<=>B). If the CheckBox in the first dialog is checked, the CheckBox in the second dialog should also be checked. Should I use ApplicationSettings for this purpose?
DataBinding is not only the process of binding between two dependency properties, but also between one CLR and one dependency property. In fact it usually is the most common binding scenario. So what you can do is use a model object which stores the value of your ui elements and reuse it in another view or another control.
First of all i advise you not to use a DependencyObject derived object as your data holder, while its still valid it has it cons aswell.
First our data object, which stores just our data. Please lookup how to implement the INotifyPropertyChanged interface, because i left the implementation for readability.
class DataHolder : INotifyPropertyChanged
{
public bool MyValue
{
get{return mMyValue;}
set{mMyValue = value; RaiseProperty("MyValue");}
}
private bool mMyValue;
}
This object can now be easily bound to your ui elements with the help of the UI DataContext. Its vital to know that the DataContext is an inherited dependency property, which means that in the tree of controls, if an element doesn't have a set datacontext it automatically gets the datacontext of his parent. Think of a login dialog with user name and password. If you have a simple model with two properties like user name and password, you just set this model as the datacontext to the dialog and all controls can bind to these properties. Back to your example, you just set an instance of DataHolder to your window datacontext property
public MainWindow()
{
InitializeComponents();
var model = new DataHolder();
DataContext = model;
}
and now you can use bindings in your xaml like this
<CheckBox IsChecked="{Binding MyValue, Mode=TwoWay}"/>
Multiple controls can bind to the same property, and you can use the same model when you open another view.
One last advice, you should read the DataBinding chapter of the msdn and its a good idea to look into the MVVM pattern because its widely used with WPF to great success. We use it for a rather large application and are very satisfied with it.
So here is the problem I'm beating my head against: I have a custom user control that exposes two dependency properties that are bound to my ViewModel. In my ViewModel I have an instance of a class that holds multiple properties that express values that relate to the user control as well as to items that control manipulates. Here's a bit of sample code to explain it visually so here is a simple sample of my control, it's a Slider that is combined with a checkbox that allows the user to lock the slider.
<custom:SliderControl IsLocked="{Binding Path=CustomClass.IsLocked, Mode=TwoWay}" SliderValue="{Binding Path=CustomClass.Value, Mode=TwoWay}" />
IsLocked and SliderValue are dependency properties that effectively manipulate the checkbox and slider that are contained in the custom control. All of the control functions work as intended, except for the bindings to the class I've defined. If I create individual properties, as in one int property and one bool property the bindings work as intended. However I have five sliders, and each slider in my actual code has five properties that tie in to them. I'm trying to eliminate code duplication by creating a class to hold these properties in a reusable object shrinking my 25 properties down to 5 class instances.
My CustomClass inherits ObservableObject and has a bool property and int property named IsLocked and SliderValue respectively. For more visual aids here is what it looks like:
public class CustomClass : ObservableObject
{
public const string SliderValuePropertyName = "SliderValue";
private int _sliderValue= 0;
public int SliderValue
{
get
{
return _sliderValue;
}
set
{
if (_sliderValue== value)
{
return;
}
_sliderValue= value;
RaisePropertyChanged(SliderValuePropertyName );
}
}
public const string IsCheckedPropertyName = "IsChecked";
private bool _isChecked = false;
public bool IsChecked
{
get
{
return _isChecked;
}
set
{
if (_isChecked == value)
{
return;
}
_isChecked = value;
RaisePropertyChanged(IsCheckedPropertyName);
}
}
The ViewModel property is very similar and looks like this, an new instance of the class is created when the ViewModel loads:
public const string SliderOnePropertyName = "SliderOne";
private CustomClass _sliderOne;
public CustomClass SliderOne
{
get
{
return _sliderOne;
}
set
{
if (_sliderOne== value)
{
return;
}
_sliderOne= value;
RaisePropertyChanged(SliderOnePropertyName );
}
}
Why won't the updating of the dependency property that is bound to the property in the class update properly? Is it because you can't properly update the class instance property by itself and instead have to update the entire class instance whenever changes occur? Or do I need to further customize the setter in this ViewModel property? As it sits now changing the slider value or checkbox never hits the bound property at all and nothing errors out when debugging.
EDIT: I've also surrounded the control in a Border and set the Border UIElement's DataContext to that of the class and then subsequently applied the more simple path binding to the underlying custom control. This however did not have any effect on my problem.
I'm a homegrown programmer so I often miss things when putting code together and I'm guessing this is the case here, unless what I'm trying just won't work.
Any help would be greatly appreciated.
EDIT: So I've been toying around with using a custom event that will let me know when the specific property of the custom control changes and then having that event wired up in my ViewModel to update the existing class. This works but still creates code duplication as now I have to have 10 events, 2 events per control, one to check for when the value of the slider changes and the other to detect when the checkbox IsChecked value changes. This code duplication exists since you can't route multiple command parameters (like a simple string identifier for which slider is being manipulated as well as the value you want to use in the code). This limitation means I can't just use 2 events that differentiate between which control is undergoing changes within the defined method as exposing the physical control to the ViewModel breaks the MVVM pattern. Using a class as the datacontext for the user control made it so I didn't care what control was being manipulated as they each had their own class instance. Using events this unravels the MVVM pattern as now I need to know which of the five controls is being manipulated by the user.
It can't be this hard to use a class in property bindings. I have to be missing something remedial.
here is a full example:
public partial class MainPage : UserControl
{
public MainPage()
{
InitializeComponent();
this.DataContext = new ViewModel();
}
}
public class ViewModel
{
public SliderValues slv { get; private set; }
public ViewModel()
{
slv = new SliderValues();
}
}
public class SliderValues : INotifyPropertyChanged
{
bool _isLocked = false;
public bool IsLocked
{
get { return _isLocked; }
set
{
_isLocked = value;
OnPropertyChanged("IsLocked");
}
}
int _theValue = 5;
public int TheValue
{
get { return _theValue; }
set
{
_theValue = value;
OnPropertyChanged("TheValue");
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string prop)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(prop));
}
}
Now the xaml:
<UserControl x:Class="TestBindings.MainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="400">
<Grid x:Name="LayoutRoot" Background="White">
<Slider Height="23" HorizontalAlignment="Left" Margin="114,138,0,0" Name="slider1" VerticalAlignment="Top" Width="100"
DataContext="{Binding slv}" Value="{Binding TheValue, Mode=TwoWay}"/>
</Grid>
</UserControl>
May be there is just a syntactical error. Try this
{Binding Path=CustomClass.IsLocked, Mode=TwoWay}
Try this...<custom:SliderControl DataContext="{Binding CustomClass}" IsLocked="{Binding IsLocked, Mode=TwoWay}" SliderValue="{Binding Value, Mode=TwoWay}" />
I have been trying to create a fairly simple application in WPF following the MVVM development pattern but I have been going crazy over how difficult it seems to be to do simple things. I have already created this app in Forms and had it successfully running, but my boss requested I rewrite the interface in WPF as a demo of the technology. I decided to try to follow as many best practices as I can in order to make the app and code as educational as possible. My current dilemma is using a listbox to run some code every time the selection changes. I'm ready to just use the code-behind with an event to call the method on the view-model. To me this seems to still be essentially MVVM since no logic is executing. Thanks for any help/insight.
You can do that simply binding selecteditem property of listbox... on selection change a setter in the view model will be called and you can do what ever you want...
Here is a sample which will help you
XAML
<Grid Canvas.Left="0" Canvas.Bottom="0" Height="300" Width="300" Background="Bisque">
<ListBox ItemsSource="{Binding Employes}" SelectedItem="{Binding SelectedEmploye}"/>
</Grid>
View Model
public class ViewModel : ViewModelBase
{
private List<Employee> _employes;
public List<Employee> Employes
{
get { return _employes; }
set { _employees = value; OnPropertyChanged("Employes"); }
}
private Employee _selectedEmploye;
public Employee SelectedEmploye
{
get { return _selectedEmploye; }
set
{
_selectedEmployee = value;
OnPropertyChanged("SelectedEmploye");
}
}
}
View model base
public class ViewModelBase : INotifyPropertyChanged
{
protected virtual void OnPropertyChanged(string propertyName)
{
if (this.PropertyChanged != null)
{
this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
public event PropertyChangedEventHandler PropertyChanged;
}
Employee Class
public class Employee : ViewModelBase
{
private string _name;
public string Name
{
get { return _name; }
set { _name = value; }
}
}
In your ViewModel you can create a Property "SelectedItem". Bind then the SelectedItem-property if your ListBox to your property.
If it's a POCO clr-property (INotifyPropertyChanged), then you can trigger your code from the properties setter.
IF it's a DependencyProperty, you have to add a PropertyChangedCallback and trigger your code from there.
Don't be afraid to use code-behind. No code-behind is a guideline to avoid too much logic being placed in the view, not a hard and fast rule. In this case (as others have suggested) you can bind the SelectedItem property to some property on your viewmodel. With non-data-related events, my recommendation would be to handle the event as normal and delegate execution logic to the viewmodel.
In my opinion, design patterns should always be taken as rule of thumb and used with some judgement as it's quite easy to apply them too strictly in areas where they don't belong, which usually makes things worse.
Checkout the EventToCommand behavior in Galasoft MVVM Light
Here's the SO post
you can bind to ListBox.SelectedItem to get the selected item in your vm.