How properly use binding in wpf? - c#

Let say I got app that is going to read data from database.
So in c# i got class like this:
public class Employee {
public string Name { get; set; }
public string Title { get; set; }
}
And my wpf window looks like:
<TextBlock Margin = "10" Width = "100" Text = "{Binding Name}" />
As I understand, to display in my window name of the employee I should use DataContext to bind object Employee with my wpf:
public partial class MainWindow : Window {
public MainWindow() {
InitializeComponent();
DataContext = new Employee(...);
}
Ok, but what if i have another textbox to display object from another class?
public class Movie {
public string MovieTitle { get; set; }
}
<TextBlock Margin = "10" Width = "100" Text = "{Binding MovieTitle}" />
Where should I use DataContext to bind this all, that two textbox are display on my screen, and both display properties from another object?

It's not a must, but in WPF it is recommended to follow MVVM design patterns.
This means you need a View (as you created), a Model and a ViewModel.
The View should have a DataContext point to a ViewModel, and the binding in the View's XAML should be pointing to the ViewModel's properties.
So in your case, you can create a "main" ViewModel as your main DataContext object, and this ViewModel can have a string property called "EmployeeName" (or Employee ViewModel object with property "Name") and then the property can be assigned, or fetch the information directly from the Model.
Do not forget to fire a property change notification from the setter of any view model property, e.g. the PropertyChanged event of the INotifyPropertyChanged interface.
public string EmployeeName
{
get { return _employeeModel.Name; }
set
{
_employeeModel.Name = value;
PropertyChanged?.Invoke(
this, new PropertyChangedEventArgs(nameof(EmployeeName)));
}
}
If you're following MVVM properly:
Your ViewModel must not know the View, it can only know Models or other ViewModels.
Your Model must not know the View or the ViewModel, it can only know other Models.

As others were trying to point out, add another thing to the main class. Or, as Yair mentioned to create a view model. Try to think of it this way.
public class MainWindowViewModel
{
public MainWindowViewModel()
{
OneEmployee = new Employee();
OneMovie = new Movie();
}
public Employee OneEmployee {get; set;}
public Movie OneMovie {get; set;}
// any other things you want to expose, button relay commands labels, etc...
public string SomeOtherThing {get; set;}
}
Now, in your MainWindow (or whatever other secondary window you develop), in its constructor, just set
DataContext = new MainWindowViewModel();
Now, back to your XAML Your xaml's data context is to the overall view model that has BOTH objects, a label, and whatever else you want. When binding and its all in the same level in the xaml, you can do binding to OneEmployee.Name, or OneMovie.Title, or whatever other parts going on.
However, let say you are sectioning off the display and have one area designated for the employee information and another based on a movie. You can set a data context to that sub-component to the object in question, so the inner controls are now defaulted to that. Removes the need of "object.property" notation within. Ex:
<UserControl DataContext="{Binding OneEmployee}">
<Grid>
<define your grid rows/columns for example>
<Label Content="Name" Grid.Row="0" Grid.Column="0" />
<TextBox Width="100" Grid.Row="0" Grid.Column="1"
Text="{Binding Name}" />
</Grid>
</UserControl>
Notice above the entire "UserControl" has its default data context to just the OneEmployee object. So everything within can refer to properties exposed directly on that object. In this example, the entire context of the user control is to show information about the one employee and not the movie. Same can be done for a single movie.
I actually like doing it this way and create individual user controls with inner grid type content to span its own rows/columns. Then, when I put them into a main view area, I dont have to worry about messing with other nested controls, grids. The single user-control is all self-contained with all its alignments, borders, etc. If I need to adjust employee info layout, I just change that one class, not the outer view that the employee is displayed within.

Related

Why can't I bind a string?

Trying to wrap my old brain around WPF data binding.
Starting with the simplest scenario I could think of: An example online that worked, and I then simplified it.
I bound a simple string property to a TextBlock. Didn't work. When I then encapsulated that string inside a class of my own, it worked just fine!
Question: Why doesn't my string property work, but my class does?
<Window x:Class = "DataBindingOneWay.MainWindow"
xmlns = "http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x = "http://schemas.microsoft.com/winfx/2006/xaml"
Height = "350" Width = "600">
<Grid>
<StackPanel Orientation = "Vertical" HorizontalAlignment="Center" Margin="0,100,0,0">
<TextBlock Text="{Binding Name1}" />
</StackPanel>
</Grid>
</Window>
using System.Windows;
namespace DataBindingOneWay
{
public partial class MainWindow : Window
{
public class Employee
{
public string? Name1 { get; set; } = "Paul";
}
public string? Name1 { get; set; } = "Peter";
public MainWindow()
{
InitializeComponent();
DataContext = new Employee();
// DataContext = Name1;
}
}
}
Bindings operate on a data context that is set on elements deriving from FrameworkElement through the DataContext property. A data context is inherited in the tree of elements.
When data binding is declared on XAML elements, they resolve data binding by looking at their immediate DataContext property. The data context is typically the binding source object for the binding source value path evaluation. You can override this behavior in the binding and set a specific binding source object value. If the DataContext property for the object hosting the binding isn't set, the parent element's DataContext property is checked, and so on, up until the root of the XAML object tree. In short, the data context used to resolve binding is inherited from the parent unless explicitly set on the object.
If there is no data context set or you need to refer to other elements as bindings source, you need to parameterize a Binding with Source, ElementName or RelativeSource.
Bindings can be configured to resolve with a specific object, as opposed to using the data context for binding resolution.
Let use review how to bind a property defined in your MainWindow. There is no data context assigned initially. Therefore, all bindings to Name1 will fail. What can we do? Here are a few options (there are more).
Set the DataContext of MainWindow to this in code-behind.
public MainWindow()
{
InitializeComponent();
DataContext = this;
}
This means all of the controls inherit the MainWindow as binding source. Consequently, we can bind the Name1 property like this:
<TextBlock Text="{Binding Name1}"/>
Here Name1 is the name (or property path) of the property to bind on the binding source. So if we simply assigned Name1 directly as DataContext of the window, then we would need to refer to the binding source itself, which is what {Binding} without a property path does.
public MainWindow()
{
InitializeComponent();
DataContext = Name1;
}
<TextBlock Text="{Binding}"/>
If you do not set a DataContext, you can still refer to elements using ElementName (requires an x:Name assigned to the target control) or RelativeSource which refers to an element in the element tree from the current element traversing parents until the root is reached.
<TextBlock Text="{Binding Name1, RelativeSource={RelativeSource AncestorType={x:Type local:MainWindow}}}"/>
This example translates to: Search a binding source of type MainWindow up the element tree and if you find it, bind to its Name1 property. As you can see, this does not necessarily need a data context if the property is defined on the control itself like in your example. Nevertheless, generally you would use the DataContext property if nothing prevents it, since it is conveniently inherited.
I hope you saw that there is nothing really magical about it. Regarding the binding sytax (which can be confusing), there is a good documentation that should take away lots of your question marks:
Binding path syntax
Data binding overview (WPF .NET)
I got to think: WHAT field, or, rather, property in the String Class actually holds the 'value' of the string?
Bonus round: You do not need to worry about that at all. You bind to the string property and WPF knows how to display it, no need to use internal structures, but for educational purposes, the documentation states:
A string is a sequential collection of characters that's used to represent text. A String object is a sequential collection of System.Char objects that represent a string; a System.Char object corresponds to a UTF-16 code unit. The value of the String object is the content of the sequential collection of System.Char objects, and that value is immutable (that is, it is read-only).
The string type exposes this character array with its indexer property Chars.
Because there is no Name1 inside the object that you are binding to, when you bind to the string. That is, there is no Name1 inside the Name1 (what would be the result of Name1.Name1?). There is, however, a Name1 inside the Employee object (what would be the result of Employee.Name1?)
If you want to access Name1 property of MainWindow you should set DataContext to this (which is MainWindow instance) instead of Name1.
using System.Windows;
namespace DataBindingOneWay
{
public partial class MainWindow : Window
{
public class Employee
{
public string? Name1 { get; set; } = "Paul";
}
public string? Name1 { get; set; } = "Peter";
public MainWindow()
{
InitializeComponent();
DataContext = this;
}
}
}
or from the other side - when you want the Name1 property to be set as DataContext of MainWindow your binding should look like this:
<Window x:Class = "DataBindingOneWay.MainWindow"
xmlns = "http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x = "http://schemas.microsoft.com/winfx/2006/xaml"
Height = "350" Width = "600">
<Grid>
<StackPanel Orientation = "Vertical" HorizontalAlignment="Center" Margin="0,100,0,0">
<TextBlock Text="{Binding}" />
</StackPanel>
</Grid>
</Window>
Thanks to Jonathan's inspiring response, I got to think: WHAT field, or, rather, property in the String Class actually holds the 'value' of the string?
I played with everything, but nothing seemed to hold the actual string! (Weird).
That's where I got a crazy idea and tried simply
<TextBlock Text="{Binding}" />
And
DataContext = Name1;
And ... that worked!

Combobox binding issue

My ComboBox does not get populated with data.
Class Employee set to public, has variables such as:
public int EmployeeID { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
Code on UserControl:
public IEnumerable<csEmployee> employeeList;
public ObservableCollection<csEmployee> _employeeSorted { get; set; }
public ucAddClient()
{
InitializeComponent();
//Establish connection
var GetMyData = new DataAccess();
//Get data by procedure
employeeList = GetMyDataPV.ExecuteStoredProc<csEmployee>("procedure", new {KeyDate = Key_to_extract});
employeeList = employeeList.Where(record => record.EmployeeLevelID > 300);
_employeeSorted = new ObservableCollection<csEmployee>(employeeList.Where(record => record != null));
}
And WPF:
<ComboBox x:Name="cbAddManager"
Foreground="#FF4D648B"
FontSize="12"
IsEditable="True"
ItemsSource="{Binding _employeeSorted}"
DisplayMemberPath="FirstName"
PreviewKeyDown="cbAddManager_PreviewKeyDown"
Width="200">
<!--<ComboBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBlock Width ="50" Text="{Binding LastName}"/>
<TextBlock Text=", "/>
<TextBlock Width ="50" Text="{Binding FirstName}"/>
</StackPanel>
</DataTemplate>
</ComboBox.ItemTemplate>-->
</ComboBox>
Do you have any idea, why ComboBoxis not populated? When I do this in code (I add it in user control class) it gets data needed.
Im not sure if Im binding it correctly?
That is because you assign a new instance of a collection to your _employeeSorted property after InitializeComponent. At that time, the binding is already set up and does not get notified that you have updated the property from null, because you do not implement INotifyPropertyChanged.
There are multiple ways to solve the issue:
Initialize the collection before InitializeComponent and work on this same collection if you intend to change it, using Clear and Add instead of creating a new instance on changes.
Implement the INotifyPropertyChanged interface and use it to notify changes to your property so that the bindings are updated the the changes are applied in the user interface, e.g.:
public partial class MyUserControl : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private ObservableCollection<csEmployee> _employeeSortedField;
public ObservableCollection<csEmployee> _employeeSorted
{
get => _employeeSortedField;
set
{
if (_employeeSortedField == value)
return;
_employeeSortedField = value;
OnPropertyChanged();
}
}
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
Expose a depenedency property for the collection instead and bind it to a collection in your view model that is passed as data context of the UserControl, thus moving the data access out it and separating the view from the business logic and data (recommended, see below MVVM).
Another issue might be that you do not set your data context to the UserControl itself in XAML (which is not recommened by the way, although it might solve your issue). In this case, the binding is unable to resolve the property at runtime (a binding error will be shown in the output window).
<UserControl x:Class="YourProject.YourControl"
...
DataContext="{Binding RelativeSource={RelativeSource Self}}">
As a note, it seems that you mix your business logic with your UserControl (view). Leverage the MVVM design pattern to create view models and seprate both concerns instead. Furthermore, if you set the data context of your UserControl to itself, you break data context inheritance.

Can't add children to Grid when binding to VM with Caliburn Micro

I want to add (any) elements to a grid from my view model in my Caliburn.Micro application (it's really a Revit add-in so not quite an application, but should work the same).
I'm having a hard time wrapping my head around certain aspects of the MVVM model and binding data to elements in the view...
My ViewModel
public class MyViewModel : Screen
{
private Grid _myGrid;
public Grid MyGrid
{
get { return _myGrid; }
set
{
_myGrid = value;
NotifyOfPropertyChange(() => MyGrid);
}
}
public MyViewModel()
{
MyGrid = new Grid();
var label = new Label { Content = "Hello!" };
MyGrid.Children.Add(label); // I know this isn't MVVM but how can I do basically this?
}
}
My View
<Window x:Class="MyProject.MyView"
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"
xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"
xmlns:cal="http://www.caliburnproject.org"
xmlns:xctk="http://schemas.xceed.com/wpf/xaml/toolkit"
xmlns:local="clr-namespace:MyProject.Views"
mc:Ignorable="d">
<Grid x:Name="MyGrid">
<!-- Here goes stuff from view model -->
</Grid>
</Window>
I know this is all wrong... I need help getting elements into my grid element from the view model.
The general approach you are taking here is wrong as you are creating View elements in your ViewModel. With MVVM, you still want to design your UI (View) in XAML. In your case you would still create the Grid and the Label in your XAML in most cases. Think of what you are doing with this approach.
You have a data model that stores the data properties and notifies of changes. Think of this as the structural side of the data.
public class MyModel : INotifyPropertyChanged
{
private string labelValue;
public string LabelValue
{
get { return labelValue; }
set
{
labelValue = value;
NotifyOfPropertyChange(() => LabelValue);
}
}
//Property changed handler logic below.
//I assume you have something based on your code.
}
Now we create a ViewModel that does stuff with the data. This is what the view is bound too.
public class MyViewModel
{
//Add a property for the model you created
public MyModel NewModel {get;set;}
//Load the NewModel values when the view model is created.
public MyViewModel()
{
NewModel = new MyModel(){LabelValue="Hello World"};
}
}
Ok so we have a data Model, and we have a ViewModel to actually use the Model and fill it with values. Now lets create the UI (View) and bind to the ViewModel.
In your Window you will create a label and simply bind your desired ViewModel property. In this case we will your your models LabelValue property.
<Grid>
<Label Content="{Binding NewModel.LabelValue}"/>
</Grid>
Now we simply set the datacontext in the UI codebehind
public MyViewModel myViewModel = new MyViewModel(); //Create the ViewModel
public MyWindow()
{
InitializeComponent();
//Set the DataContext to the ViewModel
DataContext = myViewModel;
}
When you fire up the program the label content should be whatever you set it as in your ViewModel MyModel.LabelValue property. This is only a simple explanation of very large concept, but it should give you an idea of how it works. There are also many techniques and styles of MVVM people use, but this concept of is MVVM in its most basic form. Hopefully this gives you an idea and starts you on the right path.
The goal is to run your logic in you ViewModel so that the Views and Models can be built with minimal code. If you properly implement INotifyPropertyChanged all values on your View automatically update with no additional code or UI thread locks. Though the concept seems bulky at first, it saves a lot of time and effort in the long run.

WPF how to change label of property from dynamic object in MVVM

I have a program that when you click the button, it creates a person with random attributes.
If the content of the label changes with every different object (person) created, how do you define that in true MVVM style? I can't have the viewmodel control the view, right? So i can't
label.Content = person.hair_Color;
public class Person()
get set hair_Color, get set shirt_color, yadda yadda
Because there can be either 1 or an infinite amount of people, how do i dynamically add the content of a label, if i don't know how many there will be?
In 'true MVVM style', you would have something like:
Views
MainView containing:
A button "Add Person" <Button Command={Binding AddPerson}/>
A list containing some "PersonView" <ListBox ItemsSource="{Binding Persons}"/>
PersonView containing:
A label "Shirt" <TextBlock Text="{Binding Shirt}"/>
A label "Hair" <TextBlock Text="{Binding Hair}"/>
A rectangle (for the example) "ShirtGraphic" <Rectangle Background="{Binding Shirt, Converter={stringToColorConverter}/>
A rectangle "HairGraphic" <Rectangle Background="{Binding Hair, Converter={stringToColorConverter}/>
StringToColorConverter class, returning a color from a string
ViewModels
MainViewModel containing:
An observable collection property of PersonViewModel "Persons" public ObservableCollection<PersonViewModel> Persons { get; set; }
A command "AddPerson" public Command AddPerson { get; set; }
PersonViewModel containing:
A string property "Shirt" public string Shirt { get; set; }
A string property "Hair" public string Hair { get; set; }
This is pretty much just a mockup of what you would actually have, since implementation depends on the framework used, but the idea is here. You bind, you convert, etc.
It doesn't implement any INotifyPropertyChanged or ICommand
No DataTemplate is set for the ListBox (to actually display some PersonView)

How to bind a common property of two ViewModels dynamically? (MVVM, ViewModel-First )

A ViewModel1 takes data source dynamically from a Model by importing a .csv file containing data values and their distances. The ViewModel1 has two parameterized constructors. It creates a scatter series and stores it in the Series Collection.
private IEnumerable<Series> _myScatterSeries = new Collection<Series>();
A View1 is created in response to ViewModel1 using DataTemplates. View1 binds to MyScatterSeries property of ViewModel1 and shows the series representation in scatter chart.
View1.xaml:
<Grid Loaded="Grid_Loaded">
<ext:ChartExtension Style="{StaticResource ChartStyle1}" SeriesSource="{Binding MyScatterSeries}" />
</Grid>
I want to create a new View window (View2) and it should load the same scatter series created by ViewModel1 dynacamically, when I open View2 window. I tried using the same above code in View2, but it only shows chart, but now series data points. How can I bind MyScatterSeries property of the ViewModel1 to View2 dynamically?
I cannot use the following because it has problem with constructor arguments.
View2.xaml-
<UserControl.DataContext>
<vm:ViewModel1/>
</UserControl.DataContext>
I also tried using DataTemplate, adding stack panel and wrapping the two Views, but it is not working.
As it is a ViewModel-First approach, I have created a new ViewModel (ViewModel2) for View2. But I don't know how to bind MyScatterSeries of ViewModel1 to ViewModel2, using code.
Also, Can I use the same code of View1 to show scatter series in View2?
My first thought is to have one ApplicationViewModel which contains both ViewModel1 and ViewModel2, and takes control of syncing the data.
Here's a very rough example that should give you the general idea
public class ApplicationViewModel
{
ViewModel1 ViewModel1 { get; set; }
ViewModel2 ViewModel2 { get; set; }
public ApplicationViewModel()
{
ViewModel1 = new ViewModel1(someParameters);
ViewModel2 = new ViewModel2(otherParameters);
ViewModel1.PropertyChanged += VM1_PropertyChanged;
}
private VM1_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
if (e.PropertyName == "ScatterSeries")
ViewModel2.ScatterSeries = ViewModel1.ScatterSeries;
}
}
You could also use this same technique to have both objects point to the same object if you want (no idea how this data is updated or maintained by the user)
public class ApplicationViewModel
{
ViewModel1 ViewModel1 { get; set; }
ViewModel2 ViewModel2 { get; set; }
private IEnumerable<Series> _myScatterSeries;
public ApplicationViewModel()
{
_myScatterSeries = new Collection<Series>();
ViewModel1 = new ViewModel1(someParameters, _myScatterSeries);
ViewModel2 = new ViewModel2(otherParameters, _myScatterSeries);
}
}
The other option would be to use some kind of messaging system to broadcast a message when VM1 data changes, and VM2 would subscribe to those messages and take control of syncing the data. That's a big subject to go into, but I have a quick overview on my blog at Communication between ViewModels with MVVM if you're interested
And last of all, you could just make sure both View1 and View2 have their DataContext set to ViewModel1 assuming there are no other differences. For example,
<DataTemplate x:Key="View1">
<vw:View1/>
</DataTemplate>
<DataTemplate x:Key="View2">
<vw:View2 />
</DataTemplate>
...
<ContentControl Content="{Binding ViewModel1}" ContentTemplate="{StaticResource View1}" />
<ContentControl Content="{Binding ViewModel1}" ContentTemplate="{StaticResource View2}" />
I don't see how your .DataContext is set in your code above, so can't provide any relevant code sample for that, but it's always an option.
You can programmatically set the DataContext for a View in the constructor like this:
//Constructor
public View2()
{
InitializeComponent();
DataContext = new ExampleViewModel();
}
You could pass in the ViewModel you want to create to the constructor, or store a global variable which contains the ViewModel.

Categories