I'm learning WPF and investigating DataBinding. I want to see how to specify the DataBinding in XAML rather than in C# but cant figure out what I'm doing wrong in the example below.
(I know there are many questions like this already, but I've gone through them all but cant get any of the suggestions to work).
<Window x:Class="DataBinding2.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525"
x:Name="theMainWindow"
xmlns:local="clr-namespace:DataBinding2"
>
<StackPanel>
<WrapPanel Name="WrapPanel1" Orientation="Vertical" Margin="10" >
<!--// Tried this but get error: The type 'local:person2' was not found. -->
<WrapPanel.DataContext>
<local:person2 />
</WrapPanel.DataContext>
<TextBlock Text="{Binding Path=FirstName}"/>
</WrapPanel>
namespace DataBinding2
{
public partial class MainWindow : Window
{
public Person person2;
public MainWindow()
{
person2 = new Person()
{
FirstName = "Bob",
};
InitializeComponent();
// This works - but want to know what alternative is to do it in XAML
//WrapPanel1.DataContext = person2;
}
}
public class Person
{
public string FirstName { get; set; }
public int Age { get; set; }
}
You can set DataContext to only instance and not directly to property within some instance from XAML.
For that to work first make person2 a property since binding only works with properties at least for instance objects:
public Person person2 { get; set; }
and then you can set DataContext in XAML like this:
<WrapPanel Name="WrapPanel1" Orientation="Vertical" Margin="10"
DataContext="{Binding person2, ElementName=theMainWindow}">
Related
I'm developing a WPF application using caliburn.micro MVVM framework..
In-order to develop a search screen, I need to dynamically load fields into the view, based on model properties.
Consider below view and view model:
SearchViewModel
SearchView
Let's assume T is a type of Product in below example.
public class SearchViewModel<T>
{
public T Item{get;set;}
}
public class Product
{
public int Id{get;set;}
public string Name{get;set;}
public string Description{get;set;}
}
I have a user control called SearchView.xaml with no contents on it.
Whenever View is loaded new fields should be added to the view and field should be bound to the properties.
According to above code example, there are 3 public properties in the Product class, therefore 3 TextBoxes should be added to the view dynamically. When user enters data in the text field, corresponding property should be updated.
Is this possible?
Can any experts help me to achieve this by providing some examples?
I would propose going about this differently. Instead of thinking about dynamically adding properties to a view / model, I would think about adding information about those properties to a list on the viewmodel. That list would then be bound to an ItemsControl with a template that looks like a TextBox.
So your view-model would have a property on it for the "thing" you want to examine. In the setter for this property, use reflection to enumerate the properties you are interested in, and add an instance of some kind of FieldInfo class (that you create) to the list of properties with the binding.
This has the benefit of keeping everything all MVVM compatible too, and there is no need to dynamically create controls with your own code.
The example below uses my own MVVM library (as a nuget package) rather than caliburn.micro, but it should be similar enough to follow the basic idea. The full source code of the example can be downloaded from this BitBucket repo.
As you can see in the included screenshots, the search fields are created dynamically on the view without any code in the view. Everything is done on the viewmodel. This also gives you easy access to the data that the user enters.
The view-model:
namespace DynamicViewExample
{
class MainWindowVm : ViewModel
{
public MainWindowVm()
{
Fields = new ObservableCollection<SearchFieldInfo>();
SearchableTypes = new ObservableCollection<Type>()
{
typeof(Models.User),
typeof(Models.Widget)
};
SearchType = SearchableTypes.First();
}
public ObservableCollection<Type> SearchableTypes { get; }
public ObservableCollection<SearchFieldInfo> Fields { get; }
private Type _searchType;
public Type SearchType
{
get { return _searchType; }
set
{
_searchType = value;
Fields.Clear();
foreach (PropertyInfo prop in _searchType.GetProperties())
{
var searchField = new SearchFieldInfo(prop.Name);
Fields.Add(searchField);
}
}
}
private ICommand _searchCommand;
public ICommand SearchCommand
{
get { return _searchCommand ?? (_searchCommand = new SimpleCommand((obj) =>
{
WindowManager.ShowMessage(String.Join(", ", Fields.Select(f => $"{f.Name}: {f.Value}")));
})); }
}
}
}
The SearchFieldInfo class:
namespace DynamicViewExample
{
public class SearchFieldInfo
{
public SearchFieldInfo(string name)
{
Name = name;
}
public string Name { get; }
public string Value { get; set; } = "";
}
}
The view:
<Window
x:Class="DynamicViewExample.MainWindow"
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:local="clr-namespace:DynamicViewExample"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
Title="MainWindow"
Width="525"
Height="350"
d:DataContext="{d:DesignInstance local:MainWindowVm}"
mc:Ignorable="d">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<ComboBox
Grid.Row="0"
ItemsSource="{Binding Path=SearchableTypes}"
SelectedItem="{Binding Path=SearchType}" />
<ItemsControl Grid.Row="1" ItemsSource="{Binding Path=Fields}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Path=Name}" />
<TextBox Width="300" Text="{Binding Path=Value}" />
</StackPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
<Button Grid.Row="2" Command="{Binding Path=SearchCommand}">Search</Button>
</Grid>
</Window>
The model classes:
class User
{
public string FirstName { get; set; }
public string LastName { get; set; }
public string PhoneNumber { get; set; }
public string Id { get; set; }
}
class Widget
{
public string ModelNumber { get; set; }
public string Name { get; set; }
public string Description { get; set; }
}
Here is a basic example of how you could generate a TextBox per public property of the T in the control using reflection.
SearchView.xaml:
<Window x:Class="WpfApplication4.SearchView"
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:local="clr-namespace:WpfApplication4"
mc:Ignorable="d"
Title="SearchView" Height="300" Width="300">
<StackPanel x:Name="rootPanel">
</StackPanel>
</Window>
SearchView.xaml.cs:
public partial class SearchView : UserControl
{
public SearchView()
{
InitializeComponent();
DataContextChanged += SearchView_DataContextChanged;
DataContext = new SearchViewModel<Product>();
}
private void SearchView_DataContextChanged(object sender, DependencyPropertyChangedEventArgs e)
{
if (e.NewValue != null)
{
Type genericType = e.NewValue.GetType();
//check the DataContext was set to a SearchViewModel<T>
if (genericType.GetGenericTypeDefinition() == typeof(SearchViewModel<>))
{
//...and create a TextBox for each property of the type T
Type type = genericType.GetGenericArguments()[0];
var properties = type.GetProperties();
foreach(var property in properties)
{
TextBox textBox = new TextBox();
Binding binding = new Binding(property.Name);
if (!property.CanWrite)
binding.Mode = BindingMode.OneWay;
textBox.SetBinding(TextBox.TextProperty, binding);
rootPanel.Children.Add(textBox);
}
}
}
}
}
The other option will obviously be to create a "static" view for each type of T and define the TextBox elements in the XAML markup as usual.
I'm having trouble with binding the ItemsSource of a listbox to a collection of objects and then displaying a property of those objects as the list items.
My XAML code:
<Window 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"
x:Class="CaliburnMicroBasic.ShellView"
d:DesignWidth="358" d:DesignHeight="351">
<Grid Width="300" Height="300" Background="LightBlue">
<ListBox ItemsSource="{Binding ListOfPeople}">
<ListBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding PersonName}"/>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</Grid>
</Window>
My ViewModel:
namespace CaliburnMicroBasic {
using Caliburn.Micro;
using System.Collections.ObjectModel;
using System.Windows;
public class ShellViewModel : Screen, IShell
{
public Person SelectedPerson{ get; private set; }
public ObservableCollection<Person> ListOfPeople{ get; private set; }
public ShellViewModel()
{
ListOfPeople = new ObservableCollection<Person>();
ListOfPeople.Add(new Person("Name 1"));
ListOfPeople.Add(new Person("Name 2"));
ListOfPeople.Add(new Person("Name 3"));
ListOfPeople.Add(new Person("Name 4"));
}
}
public class Person
{
public string PersonName { get; private set; }
public Person(string personName)
{
_personName = personName;
}
}
}
As you can see, I'm trying to have the listbox use Person.PersonName as the contents of each textblock in the listbox, but all I'm getting is four empty rows in the listbox. In other words, the listbox contains the correct number of items, but none of them are rendered correctly.
Can anyone see what I'm doing wrong?
You are never assigning anything to your PersonName property. Change your code to:
public Person(string personName)
{
this.PersonName = personName;
}
and remove your private field.
Datacontext does not work when I declare it in XAML. But the same works if set in Code.
Detailed Analysis.
My XAML
<Window x:Class="SimpleDatabindingwithclass.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525" Name="windo">
<Grid DataContext="{Binding ElementName=windo,Path=objectOfStudent}">
<Grid.RowDefinitions>
<RowDefinition Height="*"></RowDefinition>
</Grid.RowDefinitions>
<TextBox Grid.Row="0" Margin="25" Height="25" Width="100" HorizontalAlignment="Left" Name="TextBox1" Text="{Binding Path=StudentName}"></TextBox>
</Grid>
</Window>
Corresponding code.
namespace SimpleDatabindingwithclass
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
Student objectOfStudent = new Student();
objectOfStudent.StudentName = "John diley";
objectOfStudent.Address = "20, North Travilia, Washington DC.";
//not setting datacontext here since i set that in xaml
}
public class Student
{
private string studentname;
private string address;
public string Address
{
get { return address; }
set { address = value; }
}
public string StudentName
{
get{return studentname;}
set{studentname = value;}
}
}
}
}
But, the same when I use this XAML & set datacontext through code, it works!
ie, When I put something like
this.DataContext = objectOfStudent;
in MainWindow(), the application Works!
What do u think the problem is?
Binding only works with public properties, you can't bind to some local variable. Make objectOfStudent as public property of your MainWindow.
Edit:
public Student objectOfStudent { get; set; }
public MainWindow()
{
InitializeComponent();
objectOfStudent = new Student();
objectOfStudent.StudentName = "John diley";
objectOfStudent.Address = "20, North Travilia, Washington DC.";
//not setting datacontext here since i set that in xaml
}
Edit:
Also you need to implement INotifyPropertyChanged interface in the MainWindow and Student classes and raise PropertyChanged when you set the properties. That is the right way, the binding will be updated everytime you change the properties. Or a simple way: create objectOfStudent before calling InitializeComponent.
public MainWindow()
{
objectOfStudent = new Student();
objectOfStudent.StudentName = "John diley";
objectOfStudent.Address = "20, North Travilia, Washington DC.";
InitializeComponent();
//not setting datacontext here since i set that in xaml
}
This is because you are trying to use a local variable named objectOfStudent via XAML - this has no meaning in the context of XAML. XAML only accepts fields and properties, not local variables.
I want to bind a class member of an element I added to a collection to DisplayMemberPath. I bound a ObservableCollection to ComboBox.ItemSource and want to show the property name in the combobox's list which is a member of my class AxisBase.
Here is my code:
private ObservableCollection<AxisBase> axis { get; set; }
axis I use to hold elements of the following class
class AxisBase
{
...
public string name { get; set; }
...
}
This is how my xaml looks like
<ComboBox Name="comboBox_AchsenListe" DisplayMemberPath="{Binding ElementName=axis, Path=AxisBase.name}" ItemsSource="{Binding ElementName=_MainWindow, Path=axis}"</ComboBox>
Does anyone know how to bind name to DisplayMemberPath?
change DisplayMemberPath value
DisplayMemberPath="name"
SelectedValuePath="name"
and look at this question
I have created sample application for you
here the xaml
<Window x:Class="ComboBoxSample.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525">
<Grid>
<ComboBox
ItemsSource="{Binding Path=AxisBases}"
DisplayMemberPath="Name"
SelectedValuePath="Name"
Height="23" HorizontalAlignment="Left" Margin="200,134,0,0" Name="comboBox1" VerticalAlignment="Top" Width="120" />
</Grid>
here is code behind
using System.Collections.ObjectModel;
using System.Windows;
namespace ComboBoxSample
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
AxisBases = new ObservableCollection<AxisBase>
{
new AxisBase {Name = "Firts"},
new AxisBase {Name = "Second"},
new AxisBase {Name = "Third"}
};
//Set the data context for use binding
DataContext = this;
}
public ObservableCollection<AxisBase> AxisBases { get; set; }
}
public class AxisBase
{
public string Name { get; set; }
}
}
It works OK and binding also in combo box appears 3 items.
I can bind collection to treeveiw but I don't know hot bind one simle object to wpc control.
<UserControl x:Class="ReporterWpf.UserControl1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Height="300" Width="300">
<Grid>
<StackPanel>
<TextBox Name="{Binding Path=Name}"></TextBox>
<TextBox Name="{Binding Path=Age}"></TextBox>
</StackPanel>
</Grid>
</UserControl>
public Person
{
public string Name {get;set;}
public int Age {get;set;}
}
public partial class UserControl1 : UserControl
{
public UserControl1()
{
InitializeComponent();
}
public UserControl1(Person person):this()
{
Person person=new Person();
person.Age=19;
person.Name = "Patrick"
}
}
Which are magic lines of code to bind this two properties ?
You need to set the DataContext of any of the parent elements.
For example:
this.DataContext = person;
If you want to bind two people to two different panels, you'll need to set each panel's DataContext separately. (Or bind them both to a parent object that holds the people)
You just need give data context to the parent container which has binding expresions:
this.DataContext= person;
Where "person" instance of your class