How to bind DependencyProperty without using MVVM - c#

Well, I am doing a small project and I found it wasn't necessary to implemente a full MVVM.
I am trying to bind some properties in code behind, but cannot manage to make it works.
The point is the use of DependencyProperties and Binding in code behind.
I tried to follow these links and questions in SO:
Bind Dependency Property in codebehind WPF
How to: Create a Binding in Code
Bind Dependency Property, defined in Code-Behind, through Xaml to a Property in the DataContext of a UserControl
But they are related to MVVM or at least I cannot adapt the code in my case.
The example should be very simple.
MainWindow.xaml
<Label Name="_lblCurrentPath"
Style="{StaticResource CustomPathLabel}"
ToolTip="{Binding CurrentPath}"
Content="{Binding CurrentPath, Mode=TwoWay,
UpdateSourceTrigger=PropertyChanged}"/>
MainWindow.xaml.cs
public MainWindow()
{
InitializeComponent();
SetBindings();
}
#region Properties
public static readonly DependencyProperty CurrentPathProperty =
DependencyProperty.Register("CurrentPath", typeof(String), typeof(MainWindow), new PropertyMetadata(String.Empty, OnCurrentPathChanged));
public string CurrentPath
{
get { return (String)GetValue(CurrentPathProperty); }
set { SetValue(CurrentPathProperty, value); }
}
#endregion
#region Bindings
private void SetBindings()
{
// Label CurrentPath binding
Binding _currentPath = new Binding("CurrentPath");
_currentPath.Source = CurrentPath;
this._lblCurrentPath.SetBinding(Label.ContentProperty, _currentPath);
}
#endregion
#region Methods
private void Refresh()
{
MessageBox.Show("Refresh!");
}
private string Search()
{
WinForms.FolderBrowserDialog dialog = new WinForms.FolderBrowserDialog();
WinForms.DialogResult _dResult = dialog.ShowDialog();
switch(_dResult)
{
case WinForms.DialogResult.OK:
CurrentPath = dialog.SelectedPath;
break;
default:
break;
}
return CurrentPath;
}
#endregion
#region Events
private static void OnCurrentPathChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
MainWindow instance = d as MainWindow;
instance.Refresh();
}
public void OpenSearchEclipsePath(object sender, RoutedEventArgs e)
{
CurrentPath = Search();
}
public void RefreshEclipsePath(object sender, RoutedEventArgs e)
{
Refresh();
}
Any idea?
.If this is a bad practice and I should use MVVM comments are welcome, of ourse.
.Also... Related to Command property. In this case where I don't want to use a MVVM approach, is it better to register events? I found the use of custom command bindings a little bit tedious.

First, you can totally use bindings without MVVM. I wouldn't reccommend it, as the code is a lot cleaner when you use MVVM, but it can be done. All you need to do is put this line in your constructor:
this.DataContext = this;
Now your view is also your view model! Like I said, not a good idea.
Now, the code you have has a DependencyProperty in your MainWindow class. Don't do that. It serves absolutely no purpose. DPs are there so parent controls can give a binding to them. MainWindow has no parent; so a DP is useless.
All you need to do is set up a regular property:
public string CurrentPath
{
get { return currentPath; }
set
{
currentPath = value;
NotifyPropertyChanged();
}
}
And then have MainWindow implement INotifyPropertyChanged (did I mention that it makes more sense to use a simple view model?).
To answer your Command question. Yes, if you are opposed to using commands, just register for the events. However, Command is a really nice way to get user clicks into the view model without breaking MVVM. The syntax isn't that bad. If you are going the "View as a View Model" approach anyways though, Command doesn't buy you much.

Related

How to achieve dynamic binding in WPF/MVVC C#

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);

Change brushes based on ViewModel property

I have an application which has CarViewModel + view (UserControl).
What I want to achieve is to change the style of brushes when the bound DataContext Car.Status changes.
I found out how to change the brushes (in code behind of the view):
private void LoadThemeResources(bool isPrepareMode)
{
if (isPrepareMode)
{
Uri themeUri = new Uri(#"/../Resources/MyBrushes.Light.xaml", UriKind.Relative);
ResourceDictionary themeDictionary = Application.LoadComponent(themeUri) as ResourceDictionary;
this.Resources.MergedDictionaries.Add(themeDictionary);
}
else
{
this.Resources.MergedDictionaries.Clear();
}
}
By default the application and everthing has a dark theme spread over multiple files. This MyBrushes.Light overwrites some of those.
But I have no clue how I can execute the LoadThemeResources function based on a property change in the ViewModel in a MVVM friendly way.
I can do in the code behind of the view:
var vm = (CarViewModel) DataContext;
vm.Car.PropertyChanged += HandleStatusChanged;
But this is a tight coupling between View and ViewModel.
I can also do it via Messenger (From MVVM Light), but that gets broadcasted throughout the whole application and seems overkill.
Is there an other way? Or preferred way?
I would prepare some attached property (used on UserControl). Bind that property to your view-model and add code logic of LoadThemeResources in the property changed callback, something like this:
public static class ThemeService {
public static DependencyProperty IsPrepareModeProperty =
DependencyProperty.RegisterAttached("IsPrepareMode", typeof(bool), typeof(ThemeService),
new PropertyMetadata(isPrepareModeChanged));
public static bool GetIsPrepareMode(UserControl e){
return (bool) e.GetValue(IsPrepareModeProperty);
}
public static void SetIsPrepareMode(UserControl e, bool value){
e.SetValue(IsPrepareModeProperty, value);
}
static void isPrepareModeChanged(object sender, DependencyPropertyChangedEventArgs e){
var u = sender as UserControl;
u.LoadThemeResources((bool)e.NewValue);
}
}
//you need some public method of LoadThemeResources
public void LoadThemeResources(bool isPrepareMode) {
//...
}
Usage in XAML:
<UserControl ...
local:ThemeService.IsPrepareMode="{Binding Car.Status}">
<!-- ... -->
</UserControl>
You can also declare a normal DependencyProperty for your UserControl's class and use that instead of the attached property (the usage is just the same).
You could bind to a property on your ViewModel, and use an IValueConverter in your View to turn that property (whether boolean, status enumeration, whatever) into a Brush to be used.
That is, load the theme/resources in the converter (a deliberate bridge between View and ViewModel) so that your View gets the Brush it wants and your ViewModel only has to expose the 'important' information (the bits that help decide what brush to load). The decision logic is all in the converter.

Open a new Window in MVVM

Lets say I have a MainWindow and a MainViewModel, I'm not using MVVM Light or Prism in this example.
In this MainWindow I want to click a MenuItem or Button to open a NewWindow.xaml not a UserControl.
I know how to use this with UserControl to open a new UserControl in my existing Window in a ContrntControl or a Frame.
<ContentControl Content="{Binding Path=DisplayUserControl,UpdateSourceTrigger=PropertyChanged}" />
Code
public ViewModelBase DisplayUserControl
{
get
{
if (displayUserControl == null)
{
displayUserControl = new ViewModels.UC1iewModel();
}
return displayUserControl;
}
set
{
if (displayUserControl == value)
{
return;
}
else
{
displayUserControl = value;
OnPropertyChanged("DisplayUserControl");
}
}
}
In the ResourceDitionary for MainWindow I have :
<DataTemplate DataType="{x:Type localViewModels:UC1ViewModel}">
<localViews:UC1 />
</DataTemplate>
<DataTemplate DataType="{x:Type localViewModels:UC2ViewModel}">
<localViews:UC2 />
</DataTemplate>
The thing is that I want to open a new Window, not a UserControl. So I use some code like this :
private ICommand openNewWindow;
public ICommand OpenNewWindow
{
get { return openNewWindow; }
}
public void DoOpenNewWindow()
{
View.NewWindowWindow validationWindow = new View.NewWindow();
NewWindowViewModel newWindowViewModel = new NewWindowViewModel();
newWindow.DataContext = ewWindowViewModel;
newWindow.Show();
}
and then a bind OpenNewWindow to a MenuItem or Button.
I know this is not the right way, but what is the right way to do this ?
Thanks!
There are two problems you need to solve with this type of application.
Firstly, you do not want to have the View-Model creating and displaying UI components directly. One of the motivations for using MVVM is to introduce test-ability in to your View-Model, and having this class pop up new windows makes this class harder to test.
The second problem you need to solve is how to resolve the dependencies in your application, or in this instance – how to you “hook up” the View-Model to the corresponding View? A maintainable solution to this latter problem is given by the use of a DI container. A very good reference to this subject is given by Mark Seemann’s Dependency Injection in .NET. He actually also discusses how to solve the first problem too!
To solve the former problem, you need to introduce a layer of indirection to your code, to make the View-Model not dependent on a concrete implementation of creating a new window. A very simple example is given in the code below:
public class ViewModel
{
private readonly IWindowFactory m_windowFactory;
private ICommand m_openNewWindow;
public ViewModel(IWindowFactory windowFactory)
{
m_windowFactory = windowFactory;
/**
* Would need to assign value to m_openNewWindow here, and associate the DoOpenWindow method
* to the execution of the command.
* */
m_openNewWindow = null;
}
public void DoOpenNewWindow()
{
m_windowFactory.CreateNewWindow();
}
public ICommand OpenNewWindow { get { return m_openNewWindow; } }
}
public interface IWindowFactory
{
void CreateNewWindow();
}
public class ProductionWindowFactory: IWindowFactory
{
#region Implementation of INewWindowFactory
public void CreateNewWindow()
{
NewWindow window = new NewWindow
{
DataContext = new NewWindowViewModel()
};
window.Show();
}
#endregion
}
Note that you take an implementation of IWindowFactory in the constructor of your View-Model, and it is to this object that the creation of the new window is delegated to. This allows you to substitute the production implementation for a different one during testing.

Custom dependency property isn't getting binded input

I'm creating a UserControl with a DependencyProperty but the DependencyProperty isn't getting the value that the caller is passing in.
I've discovered the following during my own investigation
If I use a built-in user control, such as TextBlock, everything works. This narrows the problem down to my UserControl's implementation (as opposed to the code that calls the UserControl)
My property changed callback that I register isn't even being called (well... at least the breakpoint isn't being hit)
If only see this problem when I use a binding to provide the dependency property, so this doesn't work:
<common:MyUserControl MyDP="{Binding MyValue}"/>
but I have no problems if I get rid of the binding and hardcode the value, so this works:
<common:MyUserControl MyDP="hardCodedValue"/>
Here's my UserControl's code behind:
public partial class MyUserControl : UserControl
{
public string MyDP
{
get { return (string)GetValue(MyDPProperty); }
set { SetValue(MyDPProperty, value); }
}
public static readonly DependencyProperty MyDPProperty =
DependencyProperty.Register(
"MyDP",
typeof(string),
typeof(MyUserControl),
new FrameworkPropertyMetadata(
"this is the default value",
new PropertyChangedCallback(MyUserControl.MyDPPropertyChanged)));
public static void MyDPPropertyChanged(DependencyObject obj, DependencyPropertyChangedEventArgs e)
{
((MyUserControl)obj).MyDP = (string)e.NewValue;
}
public MyUserControl()
{
InitializeComponent();
this.DataContext = this;
}
}
And here's the xaml
<Grid>
<TextBlock Text="{Binding MyDP}"/>
</Grid>
Since I'm able to use built-in user controls such as TextBlock, I don't think that the error lies in my host code, but here it is, just so that you have a complete picture:
<StackPanel>
<common:MyUserControl MyDP="{Binding MyValue}"/>
</StackPanel>
public class MainWindowViewModel
{
public string MyValue { get { return "this is the real value."; } }
}
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
this.DataContext = new MainWindowViewModel();
}
}
This line in the UserControl is wrong:
this.DataContext = this;
This makes the UserControl its own DataContext, so the binding is looking for a property called MyValue on the UserControl, and that property does not exist. You want the DataContext to be your view-model. If you don't set it explicitly, it will inherit the DataContext from its container (the Window in this case).
Delete that line, and you'll be closer. You also don't need that callback; remove that too.
You can update your control's view code like that:
<Grid>
<TextBlock x:Name="_textBlock"/>
</Grid>
And set a _textBlock's text property in MyDPPropertyChanged method:
public static void MyDPPropertyChanged(DependencyObject obj, DependencyPropertyChangedEventArgs e)
{
var control = ((MyUserControl)obj);
control.MyDP = (string)e.NewValue;
control._textBlock.Text = control.MyDP;
}
That will do the trick.
Kindly Implement INotifyPropertyChanged, and PropertyChangedEventHandler in side the control and also the viewmodel. secondly use SetCurrentValue method to set the value inside the control class rather setting it directly

wpf usercontrol with exposed commands in more than one place within application

I obviously don't get this somewhere.
I have created a UserControl, the bare bones of which is:
private readonly DependencyProperty SaveCommandProperty =
DependencyProperty.Register("SaveCommand", typeof(ICommand),
typeof(ctlToolbarEdit));
private readonly DependencyProperty IsSaveEnabledProperty =
DependencyProperty.Register("IsSaveEnabled", typeof(bool),
typeof(ctlToolbarEdit), new PropertyMetadata(
new PropertyChangedCallback(OnIsSaveEnabledChanged)));
public ctlToolbarEdit()
{
InitializeComponent();
}
public bool IsSaveEnabled
{
get { return (bool)GetValue(IsSaveEnabledProperty); }
set { SetValue(IsSaveEnabledProperty, value); }
}
public static void OnIsSaveEnabledChanged(DependencyObject d,
DependencyPropertyChangedEventArgs e)
{
((ctlToolbarEdit)d).cmdSave.IsEnabled = (bool)e.NewValue;
}
#region Command Handlers
public ICommand SaveCommand
{
get { return (ICommand)GetValue(SaveCommandProperty); }
set { SetValue(SaveCommandProperty, value); }
}
private void cmdSave_Click(object sender, RoutedEventArgs e)
{
if (SaveCommand != null)
SaveCommand.Execute(null);
}
#endregion
Excellent. You can see what I am doing ... handling the click event of the button, and basically firing up the command.
The form (lets call that Form1 for the time being ... but note that this is actually a UserControl: common practice, I believe, in MVVM) that is hosting the control has the following line:
<ctl:ctlToolbarEdit HorizontalAlignment="Right" Grid.Row="1"
SaveCommand="{Binding Save}" IsSaveEnabled="{Binding IsValid}" />
This works great. I have an ICommand in my ViewModel called 'Save' and the ViewModel is correctly presenting the IsValid property.
So far so very good.
Now I want to have my new usercontrol also on Form2 (which is also a usercontrol - common practice, I believe, on MVVM). As it happens, Form1 and Form2 are on the screen at the same time.
It compiles, but I get a runtime exception:
'SaveCommand' property was already registered by 'ctlToolbarEdit'."
... leading me to believe that I don't get 'commands' at all.
Why can I not use my usercontrol in more than one place?
If I cannot, what would you suggest is another way to do this?
Very frustrating!
Thanks for any help.
Try making your dependency properties static. Otherwise it is getting re-registered every time you instantiate a new control. Your usage of the MVVM commands looks good otherwise and sounds like you have a good grasp on it.

Categories