WPF Binding Syntax Question - wpf

The code below is supposed to show a stack of three list boxes, each containing a list of all the system fonts. The first is unsorted, and the second and third are alphabetized. But the third one is empty. I don't see any binding error messages in the VS Output window when debugging.
The markup is:
<Window x:Class="FontList.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:FontList"
Title="MainWindow" Height="600" Width="400">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="*"></RowDefinition>
<RowDefinition Height="*"></RowDefinition>
<RowDefinition Height="*"></RowDefinition>
</Grid.RowDefinitions>
<ListBox Grid.Row="0" ItemsSource="{Binding Source={x:Static Fonts.SystemFontFamilies}}" />
<ListBox Grid.Row="1" ItemsSource="{Binding Path=SystemFonts}" />
<ListBox Grid.Row="2" ItemsSource="{Binding Source={x:Static local:MainWindow.SystemFonts}}" />
</Grid>
The code behind is:
using System.Collections.Generic;
using System.Linq;
using System.Windows;
using System.Windows.Media;
namespace FontList
{
public partial class MainWindow : Window
{
public static List<FontFamily> SystemFonts { get; set; }
public MainWindow() {
InitializeComponent();
DataContext = this;
SystemFonts = Fonts.SystemFontFamilies.OrderBy(f => f.ToString()).ToList();
}
}
}
What's wrong with the third binding?

You need to initialize SystemFonts before you call InitalizeComponent. The WPF binding has no way of knowing the property's value changed.
public MainWindow() {
SystemFonts = Fonts.SystemFontFamilies.OrderBy(f => f.ToString()).ToList();
InitializeComponent();
DataContext = this;
}
or better yet, use:
static MainWindow() {
SystemFonts = Fonts.SystemFontFamilies.OrderBy(f => f.ToString()).ToList();
}
public MainWindow() {
InitializeComponent();
DataContext = this;
}

The Bindings are created during InitializeComponent, while SystemFonts is null. After you set it, the binding has no way of knowing that the property's value has changed.
You can also set SystemFonts in a static constructor, which is probably preferable since it's a static property. Otherwise every instantiation of MainWindow will change the static property.
public partial class MainWindow : Window {
public static List<FontFamily> SystemFonts{get; set;}
static MainWindow {
SystemFonts = Fonts.SystemFontFamilies.OrderBy(f => f.ToString()).ToList();
}
...
}

Related

How to set data context for user control with prism wpf?

This my view.xaml:
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height=".5*"/>
<RowDefinition Height="0.5*"/>
</Grid.RowDefinitions>
<Grid Grid.Row="0" x:Name="grdFormSearch" HorizontalAlignment="Stretch" VerticalAlignment="Stretch">
<local:StudentUserControl HorizontalAlignment="Stretch" Height="100" VerticalAlignment="Stretch"/>
</Grid>
</Grid>
Above I added a StudentUserControl in view.xaml.
StudentUserControl.xaml.cs:
public partial class StudentUserControl : UserControl
{
public StudentUserControl(StudentViewModel ViewModel)
{
InitializeComponent();
this.DataContext = ViewModel;
}
}
StudentViewModel.cs:
public StudentViewModel(IEventAggregator eventAggregator, IUnityContainer container)
{
_eventAggregator = eventAggregator;
_container = container;
}
It is throwing an error in xaml, as it's expecting a parameterless constructor!
How to set the DataContext for the UserControl?
What is the best approach to do it?
Remove the parameter from the constructor of the view:
public partial class StudentUserControl : UserControl
{
public StudentUserControl()
{
InitializeComponent();
}
}
It shouldn't be there if you want to be able to create an instance of the view in your XAML markup like you are doing here:
<local:StudentUserControl HorizontalAlignment="Stretch" Height="100" VerticalAlignment="Stretch"/>
Besides, you shouldn't explicitly set the DataContext of the view in the code-behind anyway. The DataContext should in most cases be inherited from the parent element, i.e. view.xaml in your case, and if you explicitly set the DataContext in the constructor of the view, you break the inheritance.
If the parent view has no DataContext for some reason, you could use Prism's view model locator to create a view model:
<UserControl x:Class="WpfApplication1.StudentUserControl"
...
xmlns:prism="http://prismlibrary.com/"
prism:ViewModelLocator.AutoWireViewModel="True">
Please refer to the following link for more information about this: http://brianlagunas.com/getting-started-prisms-new-viewmodellocator/
You could also set your DataContext in the view.xaml
add your viewModel namespace
xmlns:viewModel="using:YourApp.ViewModels"
then in your xaml markup set it this way
<UserControl.DataContext>
<viewModel:StudentViewModel x:Name="ViewModel" />
</UserControl.DataContext>
you get a ViewModel property in your view, of Type StudentViewModel

Binding text property of text box to variable defined on MainWindow-WPF

I'm newbie on WPF and I have text box and button which open folder browser dialog.
When the user select folder I would like text box will contain the selected path.
So on MainWindow I added two variables:
public partial class MainWindow : Window
{
public string outputFolderPath { get; set; }
string reducedModelFolderPath { get; set; }
}
and when user selected folder path (after open folder dialog) I updated those variables by doing (for example):
outputFolderPath = dialog.SelectedPath
In MainWindow.xaml:
<TextBox x:Name="outputFolder" Width ="200" Height="30" Grid.Row="1" Grid.Column="1" Margin="5 10">
How can I bind TextBox.Text to outputFolderPath variable?
Thanks for your assitance!
You need to set DataContext of your window to this, to access your property in XAML, and after that bind to the property. As you are binding not to DependencyProperty, you should notify your binding that property has changed, which could be done by implementing INotifyPropertyChanged interface in your Window.
I've provided sample code to show the concept.
But this is very ugly, much better to use MVVM pattern instead.
MainWindow.xaml.cs
public partial class MainWindow : Window, INotifyPropertyChanged
{
public string outputFolderPath { get; set; }
public MainWindow()
{
InitializeComponent();
DataContext = this;
}
public event PropertyChangedEventHandler PropertyChanged;
private void Button_Click(object sender, RoutedEventArgs e)
{
outputFolderPath = "Some data";
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(outputFolderPath)));
}
}
MainWindow.xaml
<Window x:Class="simplest.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:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:simplest"
mc:Ignorable="d"
Title="MainWindow" Height="350" Width="525">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition />
</Grid.RowDefinitions>
<Button Click="Button_Click" Content="Go" />
<TextBox x:Name="outputFolder" Width ="200" Height="30" Grid.Row="1" Grid.Column="1" Margin="5 10" Text="{Binding outputFolderPath}"/>
</Grid>
</Window>

WPF MVVM Data Binding

Im trying to implement the MVVM Pattern i just want to have a TextBox that shows some initial text at startup.
this is my view: (dont care about the buttons and the listbox for now)
<Window x:Class="Friends.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" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<TextBox Grid.Row="0" Width="150" Text="{Binding Friend}"></TextBox>
<ListBox Grid.Row="1" Width="150"></ListBox>
<Button Grid.Row="2" Content="Previous" Width="150"></Button>
<Button Grid.Row="3" Content="Next" Width="150"></Button>
</Grid>
this is my model:
public class FriendsModel : INotifyPropertyChanged
{
private string _firstName;
public string FirstName
{
get { return _firstName; }
set
{
_firstName = value;
RaisePropertyChanged("FirstName");
}
}
public FriendsModel(string _initialName)
{
_firstName = _initialName;
}
public event PropertyChangedEventHandler PropertyChanged;
public void RaisePropertyChanged(string _newName)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null) {
handler(this, new PropertyChangedEventArgs(_newName));
}
}
}
and this is my viewmodel:
public class FriendsViewModel
{
public FriendsModel Friend { get; set; }
public FriendsViewModel()
{
Friend = new FriendsModel("Paul");
}
}
in the code behind i have:
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
DataContext = new FriendsViewModel();
}
}
my project is building without any errors but it doesnt show the text in my textbox. Can anyone help me?
thanks in advance
edit:
i changed it to
<TextBox Grid.Row="0" Width="150" Text="{Binding Friend.Firstname}"></TextBox>
its still not working.
The binding should point the FirstName property. WPF can not figure out by him self how to convert Friend class to string.
Text="{Binding Friend.FirstName}"
the Friend in the binding represents the full object, you must specify the membre...
try to replace{Binding Friend} by {Binding Friend.FirstName}
The DataContext is being set right after InitializeComponent() is called, this means that the bindings have already been setup, the textbox is correctly binding to the FirstName property but at the point of binding it's empty.
if you want the textbox to update when the property does you'll need to set DataContext before InitializeComponent()
public MainWindow()
{
DataContext = new FriendsViewModel();
InitializeComponent();
}
gives the result
Have you tried this:
public FriendsModel(string _initialName)
{
this.FirstName = _initialName;
}
Regards,

WPF: can't bind object to listbox

Trying to bind a listbox to an object. Code runs without errors but for some reason sample data doesn't appear in listbox
XAML: ucDataBindingObject.xaml
<UserControl x:Class="TheProject.UserControls.ucDataBindingObject"
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"
Name="DataBindingObject"
Width="Auto"
Height="Auto"
mc:Ignorable="d">
<Grid Width="130"
Height="240"
Margin="0">
<ListBox Name="lbObject"
Width="110"
Height="80"
Margin="10,7,-9.6,0"
HorizontalAlignment="Left"
VerticalAlignment="Top"
DisplayMemberPath="Name"
ItemsSource="{Binding ElementName=ucDataBindingObject,
Path=Clients}" />
</Grid>
</UserControl>
C#: ucDataBindingObject.xaml.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Windows.Controls;
namespace TheProject.UserControls
{
public partial class ucDataBindingObject : UserControl
{
public List<Client> Clients { get; set; }
public ucDataBindingObject()
{
Clients = new List<Client>();
Clients.Add(new Client(1, "David")); // sample data
Clients.Add(new Client(2, "Helen"));
Clients.Add(new Client(3, "Joe"));
InitializeComponent();
}
}
C# Client.cs
using System;
using System.Linq;
namespace TheProject.UserControls
{
public class Client
{
public int ID { get; set; }
public string Name { get; set; }
public Client(int id, string name)
{
this.ID = id;
this.Name = name;
}
}
}
Update your ItemsSource Binding as
ItemsSource="{Binding Path=Clients}"
and in the constructor of your view, set its DataContext after InitializeComponents as
this.DataContext = this;
there is no element named ucDataBindingObject, its the class name of your usercontrol
OR change the elementname in binding to DataBindingObject, which you named your usercontrol

Bind XAML elements to entities TwoWay

I want to bind an entity property (say Salary) to a property of a XAML element (like a TextBox.Text)
and use this binding to save Text of TextBox to salary field which is bound as a entity property to 'Text' of some TextBox.
Something like the following :
<Grid DataContext="Employee">
<TextBox Text="{Binding Path=Salary, Mode=TwoWay}"/>
</Grid>
you just can bind Properties in xaml - so your salary have to be a property and not a field. if your Employee is the class with the salary you can set datacontext to an instance of it. you can do it in xaml or codebehind or with binding.
public class Employee //implement INotifyPropertyChanged to get the power of binding :)
{
public decimal Salary {get;set}
}
view.xaml
<Grid>
<Grid.DataContext>
<local:Employee/>
</Grid.DataContext>
<TextBox Text="{Binding Path=Salary, Mode=TwoWay}"/>
</Grid>
you can set the datacontext in many ways
XAML Two-Way Binding, 'Windows Universal' style, step-by-step
In Visual Studio 2017, create a Visual C# blank app (Universal Windows). Name it 'MyProject'.
Add a class Employee to it, and then modify boilerplate code as follows:
// Employee.cs
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Text;
using System.Threading.Tasks;
namespace MyProject
{
public class Employee : INotifyPropertyChanged
{
private string salary;
public string Salary
{
get
{
return this.salary;
}
set
{
if (value != this.salary)
{
this.salary = value;
NotifyPropertyChanged();
}
}
}
public event PropertyChangedEventHandler PropertyChanged;
// This method MUST BE called by the Set accessor of each property for TwoWay binding to work.
// The CallerMemberName attribute that is applied to the optional propertyName
// parameter causes the property name of the caller to be substituted as an argument.
private void NotifyPropertyChanged([CallerMemberName] String propertyName = "")
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
// Constructor with one parameter
public Employee(string annualSalary) { salary = annualSalary; }
}
}
Notice that class Employee implements the INotifyPropertyChanged Interface.
Add class EmployeeViewModel to project, and modify boilerplate code as follows:
// EmployeeViewModel.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace MyProject
{
public class EmployeeViewModel
{
private Employee defaultEmployee = new Employee("50000");
public Employee DefaultEmployee { get { return this.defaultEmployee; } }
}
}
Modify MainPage.xaml.cs boilerplate code as follows
//MainPage.xaml.cs
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices.WindowsRuntime;
using Windows.Foundation;
using Windows.Foundation.Collections;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Controls.Primitives;
using Windows.UI.Xaml.Data;
using Windows.UI.Xaml.Input;
using Windows.UI.Xaml.Media;
using Windows.UI.Xaml.Navigation;
// The Blank Page item template is documented at https://go.microsoft.com/fwlink/?LinkId=402352&clcid=0x409
namespace MyProject
{
/// <summary>
/// An empty page that can be used on its own or navigated to within a Frame.
/// </summary>
public sealed partial class MainPage : Page
{
public MainPage()
{
this.InitializeComponent();
this.ViewModel = new EmployeeViewModel();
}
public EmployeeViewModel ViewModel { get; set; }
}
}
Modify MainPage.xaml boilerplate code as follows:
<Page
x:Class="MyProject.MainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:MyProject"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d">
<Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}" HorizontalAlignment="Center" VerticalAlignment="Center">
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions >
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<!--TextBlock will provide visual feedback that the two-way binding is working-->
<TextBlock x:Name="Control" Text="{x:Bind ViewModel.DefaultEmployee.Salary, Mode=OneWay}" Grid.Column="1" Grid.Row="1" HorizontalAlignment="Center"/>
<!--TextBox has two-way binding-->
<TextBox x:Name="Input" Text="{x:Bind ViewModel.DefaultEmployee.Salary, Mode=TwoWay}" Margin="10" Grid.Column="1" Grid.Row="2" HorizontalAlignment="Center"/>
<!--Button does nothing other than allow TextBox to lose focus-->
<Button x:Name="btn1" Content="Hello" Grid.Column="1" Grid.Row="3"
Foreground="Green"
HorizontalAlignment="Center"/>
</Grid>
</Page>
Notice, both 'Input' TextBox and 'Control' TextBlock are bound to the same Salary property of DefaultEmployee. The idea is that you edit and change the salary in the 'Input' TextBox, and then you can visually see the Two-Way binding at work, because the 'Control' TextBlock will update. This happens when the 'Input' TextBox loses focus (it is to allow the change of focus, for instance after pressing TAB key, that the 'Hello' button was added - the button in itself does absolutely nothing).
Build and Run. Modify salary, and either TAB or click button:
No you cant do like that. You cant Set the Class name to the DataContext. It should be the instance of Employee class.

Resources