WPF basic binding one object - c#

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

Related

Cant get DataBinding in XAML to work

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}">

Bind SelectedValue of UserControl to ViewModel

In my solution; I have two projects: One is a WPF UserControl Library, and the other is a WPF Application.
The usercontrol is pretty straightforward; it's a label and a combo box that will show the installed printers.
In the WPF application; I want to use this usercontrol. The selected value will be stored in user settings.
The problem I'm having is that I can't seem to get the proper binding to work. What I need to happen is to be able to set the SelectedValue of the UserControl when the MainWindow loads; as well as access the SelectedValue of the UserControl when I go to save my settings.
My code is below, could someone point me in the right direction?
PrintQueue user control:
<UserControl x:Class="WpfControls.PrintQueue"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:wpfControls="clr-namespace:WpfControls"
mc:Ignorable="d">
<UserControl.DataContext>
<wpfControls:PrintQueueViewModel/>
</UserControl.DataContext>
<Grid>
<StackPanel Orientation="Horizontal">
<Label Content="Selected Printer:"></Label>
<ComboBox ItemsSource="{Binding Path=PrintQueues, Mode=OneWay}" DisplayMemberPath="Name" SelectedValuePath="Name" Width="200" SelectedValue="{Binding Path=SelectedPrinterName, Mode=TwoWay}"></ComboBox>
</StackPanel>
</Grid>
</UserControl>
Print Queue Codebehind:
public partial class PrintQueue : UserControl
{
public static readonly DependencyProperty CurrentPrinterNameProperty =
DependencyProperty.Register("CurrentPrinterName", typeof (string), typeof (PrintQueue), new PropertyMetadata(default(string)));
public string CurrentPrinterName
{
get { return (DataContext as PrintQueueViewModel).SelectedPrinterName; }
set { (DataContext as PrintQueueViewModel).SelectedPrinterName = value; }
}
public PrintQueue()
{
InitializeComponent();
DataContext = new PrintQueueViewModel();
}
}
PrintQueue View Model:
public class PrintQueueViewModel : ViewModelBase
{
private ObservableCollection<System.Printing.PrintQueue> printQueues;
public ObservableCollection<System.Printing.PrintQueue> PrintQueues
{
get { return printQueues; }
set
{
printQueues = value;
NotifyPropertyChanged(() => PrintQueues);
}
}
private string selectedPrinterName;
public string SelectedPrinterName
{
get { return selectedPrinterName; }
set
{
selectedPrinterName = value;
NotifyPropertyChanged(() => SelectedPrinterName);
}
}
public PrintQueueViewModel()
{
PrintQueues = GetPrintQueues();
}
private static ObservableCollection<System.Printing.PrintQueue> GetPrintQueues()
{
var ps = new PrintServer();
return new ObservableCollection<System.Printing.PrintQueue>(ps.GetPrintQueues(new[]
{
EnumeratedPrintQueueTypes.Local,
EnumeratedPrintQueueTypes.Connections
}));
}
}
Main Window:
<Window x:Class="WPFApp.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:wpfControls="clr-namespace:WpfControls;assembly=WpfControls" xmlns:wpfApp="clr-namespace:WPFApp"
Title="MainWindow" Height="350" Width="525">
<Window.DataContext>
<wpfApp:MainWindowViewModel/>
</Window.DataContext>
<Grid>
<StackPanel>
<wpfControls:PrintQueue CurrentPrinterName="{Binding RelativeSource={RelativeSource AncestorType=Window}, Path=DataContext.PrinterName, Mode=TwoWay}"></wpfControls:PrintQueue>
</StackPanel>
</Grid>
</Window>
Main Window View Model:
public class MainWindowViewModel : ViewModelBase
{
private string printerName;
public string PrinterName
{
get { return printerName; }
set
{
printerName = value;
NotifyPropertyChanged(() => PrinterName);
}
}
public MainWindowViewModel()
{
PrinterName = "Lexmark T656 PS3";
}
}
Controls in a library need to expose DependencyProperties that you can bind to in your view. Just like WPF's TextBox exposes a Text property.
Your PrintQueue control doesn't expose anything, and instead keeps all its state in a viewmodel that nothing outside can access. Your MainWindowViewModel has no way of getting at the stuff inside PrintQueueViewModel.
You need to expose SelectedPrinterName as a DependencyProperty in the code behind of your PrintQueue xaml. Then in MainWindow.xaml you can bind it to MainWindowViewModel.PrinterName.
If you want to user ViewModels all the way through instead, then MainWindowViewModel should be creating PrintQueueViewModel itself so it can access the properties within.
As per your update / comment:
Unfortunately DependencyProperties don't work like that. The getters/setters aren't even used most of the time, and they should ONLY update the property itself. You're sort of halfway between two worlds at the moment.
If I were in your position, and assuming you can change the library so PrintQueue.xaml doesn't have a hardcoded VM instance in the view, I would just create the PrintQueueViewModel yourself. That's how MVVM is supposed to work:
ViewModel:
public class MainWindowViewModel : ViewModelBase
{
public PrintQueueViewModel PrintQueue { get; private set; }
public MainWindowViewModel()
{
PrintQueue = new PrintQueueViewModel();
PrintQueue.SelectedPrinterName = "Lexmark T656 PS3";
}
}
View:
<Window x:Class="WPFApp.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:wpfControls="clr-namespace:WpfControls;assembly=WpfControls" xmlns:wpfApp="clr-namespace:WPFApp"
Title="MainWindow" Height="350" Width="525">
<Window.DataContext>
<wpfApp:MainWindowViewModel/>
</Window.DataContext>
<Grid>
<StackPanel>
<wpfControls:PrintQueue DataContext="{Binding PrintQueue}"/>
</StackPanel>
</Grid>
</Window>
Again though, control libraries generally don't have view models, and expose their state via dependency properties since they're designed to be used in XAML.
Component libraries may expose view models, but in that case they wouldn't hard code the view model in the view.
Did you write the library? If not, how did the author expect people to use it?
I think with this small changes everything should work
<ComboBox ItemsSource="{Binding Path=PrintQueues, Mode=OneWay}" DisplayMemberPath="Name" Width="200" SelectedItem="{Binding Path=SelectedPrinter, Mode=TwoWay}"></ComboBox>
private System.Printing.PrintQueue selectedPrinter;
public System.Printing.PrintQueue SelectedPrinter
{
get { return selectedPrinter; }
set
{
selectedPrinter = value;
NotifyPropertyChanged(() => SelectedPrinter);
}
}
Now from the main window you can modify SelectedPrinter on the viewmodel and the change should be reflected on the view
(PrintQueue.DataContext as PrintQueueViewModel).SelectedPrinter = ...
I tried your code and your bindings of the PrintQueueView to the corresponding view model work fine. Your problem is that the MainWindowViewModel does not know about the PrintQueueViewModel and thus cannot retrieve the value of the selected printer when the main window closes (I guess that is the scenario you want to implement).
The quickest solution to your problem would be to do the following steps:
In MainWindow.xaml, give PrintQueue a Name so you can access it in the code behind
In MainWindow.xaml.cs, override the OnClosing method. In it you can retrieve the view model as follows: var viewModel = (PrintQueueViewModel)PrintQueue.DataContext;. After that you can retrieve the selected value and save it or whatever.
In the MainWindow constructor after InitializeComponent, you can retrieve your saved value from a file and set it on the PrintQueueViewModel by retrieving it the same way as in the previous step.
Whole code in MainWindow.xaml.cs:
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
// Retrieve your selected printer here; in this case, I just set it directly
var selectedPrinter = "Lexmark T656 PS3";
var viewModel = (PrintQueueViewModel)PrintQueue.DataContext;
viewModel.SelectedPrinterName = selectedPrinter;
}
protected override void OnClosing(CancelEventArgs e)
{
var viewModel = (PrintQueueViewModel)PrintQueue.DataContext;
var selectedPrinterName = viewModel.SelectedPrinterName;
// Save the name of the selected printer here
base.OnClosing(e);
}
}
Please remember that the major point of view models is the ability to unit-test GUI logic and to disconnect GUI appearance and logic. Your view models should not be able to retrieve all the possible printers of your system but should obtain these values by e.g. Dependency Injection. I would advise you to read about SOLID programming.

Unable to set DataContext through XAML, but the same can be set through Code

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.

WPF Binding a List to a DataGrid

This is my first time working with a WPF datagrid. From what I understand I am supposed to bind the grid to a public propery in my viewmodel. Below is the ViewModel code, as I step through the debugger GridInventory is getting set to List containing 2606 records however these records never show in the datagrid. What am I doing wrong?
public class ShellViewModel : PropertyChangedBase, IShell
{
private List<ComputerRecord> _gridInventory;
public List<ComputerRecord> GridInventory
{
get { return _gridInventory; }
set { _gridInventory = value; }
}
public void Select()
{
var builder = new SqlConnectionBuilder();
using (var db = new DataContext(builder.GetConnectionObject(_serverName, _dbName)))
{
var record = db.GetTable<ComputerRecord>().OrderBy(r => r.ComputerName);
GridInventory = record.ToList();
}
}
}
My XAML is
<Window x:Class="Viewer.Views.ShellView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="InventoryViewer" Height="647" Width="1032" WindowStartupLocation="CenterScreen">
<Grid>
<DataGrid x:Name="GridInventory" ItemsSource="{Binding GridInventory}"></DataGrid>
<Button x:Name="Select" Content="Select" Height="40" Margin="600,530,0,0" Width="100" />
</Grid>
</Window>
i think you need call raisepropertychanged event in your GridInventory setter so that view can get notified.
public List<ComputerRecord> GridInventory
{
get { return _gridInventory; }
set
{ _gridInventory = value;
RaisePropertyChanged("GridInventory");
}
}
The page's datacontext is not bound to an instance of the View Model. In the code behind after the InitializeComponent call, assign the datacontext such as:
InitializeComponent();
DataContext = new ShellViewModel();
I think you should use the RaisePropertyChanged in the ViewModel and Model and also should set the DataContext in the View.
<Window.DataContext>
<local:ShellViewModel />
</Window.DataContext>
You might want to consider using bind ObservableCollection to the datagrid. Then you don't need to maintain a private member _gridInventory and a public property GridInventory
//viewModel.cs
public ObservableCollection<ComputerRecord> GridInventory {get; private set;}
//view.xaml
<DataGrid ... ItemsSource="{Binding GridInventory}" .../>

How to programmatically set SelectedItem of a data-bound WPF ComboBox?

Question: Can anyone please provide a full code example that shows how one does programmatically change the SelectedItem of a data-bound WPF ComboBox without using MyComboBox.SelectedIndex?
Code sample: Here is what I currently have.
XAML:
<Window x:Class="Wpf.ComboBoxDemo.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>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<ComboBox Name="MyComboBox" DisplayMemberPath="LastName" SelectedIndex="0"/>
</Grid>
</Window>
Code-behind:
using System.Collections.ObjectModel;
using System.Windows;
namespace Wpf.ComboBoxDemo
{
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
ObservableCollection<Person> myPersonList = new ObservableCollection<Person>();
Person personJobs = new Person("Steve", "Jobs");
Person personGates = new Person("Bill", "Gates");
myPersonList.Add(personJobs);
myPersonList.Add(personGates);
MyComboBox.ItemsSource = myPersonList;
// How do I programmatically select the second Person, i.e. "Gates"?
// The best pratice must be to somehow to set something like IsCurrentlySelected on the model, so the view update automatically. But how?
MyComboBox.SelectedIndex = 1; // This works, but is there no way without using the index?
}
private class Person
{
public string FirstName { get; set; }
public string LastName { get; set; }
public Person(string firstName, string lastName)
{
FirstName = firstName;
LastName = lastName;
}
}
}
}
Similar questions: I have of course searched the Internet first, but found nothing that helped me.
Changing the SelectedItem of a enum-bound combobox inside ViewModel (MSDN)
Programmatically set ComboBox SelectedItem in WPF (3.5sp1) (Stack Overflow)
At the top of my head (I might be wrong), make the window implement INotifyPropertyChanged and add the event:
namespace Wpf.ComboBoxDemo
{
public partial class MainWindow : Window, INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public MainWindow()
{
Then add a property for the currently selected item which notifies on changes:
private Person _selected;
public Person MySelected
{
get { return _selected; }
set
{
if (value != _selected)
{
_selected = value;
if (PropertyChanged != null)
{
PropertyChanged(this,
new PropertyChangedEventArgs("MySelected"));
}
}
}
}
Now bind the combobox (the binding could be more advanced here using FindAncestor but sometimes to keep things simple I put the datacontext in code behind):
XAML:
<ComboBox
Name="MyComboBox"
DisplayMemberPath="LastName"
SelectedItem="{Binding MySelected}" />
Code behind:
public MainWindow()
{
InitializeComponent();
// ...
// this will cause the "MySelected" binding to target the correct property on this object
MyComboBox.DataContext = this;
}
I think it goes something like that. I cant test it right now but hopefully it will nudge you in the right direction.
Edit: If you want to try the "other way" of binding heres how. Expand the SelectedItem binding to look like this:
<ComboBox
Name="MyComboBox"
DisplayMemberPath="LastName"
SelectedItem="{Binding MySelected,
RelativeSource={RelativeSource FindAncestor,
AncestorType={x:Type Window}}}" />
You can now skip setting the DataContext in code behind:
public MainWindow()
{
InitializeComponent();
// ...
// this will cause the "MySelected" binding to target the correct property on this object
//MyComboBox.DataContext = this;
}
This is because that FindAncestor mode makes the ComboBox itself find the object to which property it should bind, rather than you specifically stating.
The current hot topic here at the office is which of these two ways are the best. To me its just more XAML and less code behind (or the other way around), just use the method that places the code where youre comfortable to work. I think there are some scenarios where the latter is preferred (like when you include data binding controls inside other controls), but Im just dabbling so I havent really figured those parts out yet.

Categories

Resources