Binding user control to object proporties with minimal effort - c#

I have user control:
<UserControl>
<TextBox Name="TB1"/>
<TextBox Name="TB2"/>
</UserControl>
Code behind:
public partial class MyControl : UserControl
{
public TwoStrings TsObj { get; set; }
public MyControl()
{
InitializeComponent();
}
}
Where
public class TwoStrings
{
string S1 { get; set; }
string S2 { get; set; }
}
How to bind TsObj.S1 to TB1.Text and TsObj.S2 to TB2.Text with minimal code changes? TwoStrings should not be changed. Updates on object should reflect on control. Code example please.

There's a lot of resources about wpf/silverlight databinding on the internet, even here in stackoverflow. But long story short, you can simply do this:
<UserControl>
<TextBox Name="TB1" Text="{Binding S1}" />
<TextBox Name="TB2" Text="{Binding S2}"/>
</UserControl>
public partial class MyControl : UserControl
{
public TwoStrings TsObj { get; set; }
public MyControl()
{
InitializeComponent();
this.DataContext = TsObj = new TwoStrings();
}
}
EDIT: Note that if you change properties via code in the TwoStrings instance, that will be not reflected in the textboxes, because you'll need to implement INotifyPropertyChanged interface in TwoStrings (which is the ViewModel - VM in the MVVM pattern) to get two way databinding. Otherwise, you'll get only one way databinding from your textbox to the properties on the datacontext (TwoString)

Related

x:Bind in child user controls

In WPF I use the following pattern sometimes:
public class InnerViewModel {
public int I {get ;set;}
}
public class OuterViewModel {
public InnerViewModel Inner { get; } = new InnerViewModel();
}
Outer view:
<UserControl x:Class="OuterView" ...>
<local:InnerView DataContext="{Binding Inner}"/>
</UserControl>
Inner view:
<UserControl x:Class="InnerView" ...>
<TextBox Value="{Binding I}"/>
</UserControl>
In WinUI 3 I can use x:Bind instead of Binding. That comes often with a ViewModel property in the code behind file of the view:
public sealed partial class OuterView : UserControl {
public OuterViewModel ViewModel {
get;
}
public OuterView() {
this.InitializeComponent();
this.ViewModel = App.GetRequiredService<OuterViewModel>();
}
}
and
<UserControl x:Class="OuterView" ...>
<local:InnerView DataContext="{x:Bind Inner}"/>
</UserControl>
But for InnerView I cannot use x:Bind, since it does not work with DataContext.
Is there any way to initialized something like a ViewModel-property of the InnerView in a similar way as the DataContext-binding? Or is it just not possible to apply this pattern to the x:Bind-approach?
I figured out: x:Bind works also with non-dependency properties, so I can do the following:
public sealed partial class InnerView : UserControl {
public InnerViewModel? ViewModel {
get; set;
}
public InnerView() {
this.InitializeComponent();
}
}
<UserControl x:Class="OuterView" ...>
<local:InnerView ViewModel="{x:Bind Inner}"/>
</UserControl>
and
<UserControl x:Class="InnerView" ...>
<TextBox Value="{x:Bind ViewModel.I}"/>
</UserControl>
InnerView.ViewModel is not initialized in the constructor, so it needs a setter, but it seems to work.

Bind property between sibling user controls

I have the below problem: I have two different user controls inside a parent user control. These are trainList, which holds a list of train objects and trainView, which is an user control that shows details of the selected train in the list.
My wish is to share a variable of trainList with trainView.
What I have now is:
Parent user control:
<UserControl>
<UserControl>
<customControls:trainList x:Name="trainList"></customControls:trainList>
</UserControl>
<UserControl>
<customControls:trainView x:Name="trainView"></customControls:trainView>
</UserControl>
<TextBlock DataContext="{Binding ElementName=trainList, Path=SelectedTrain}" Text="{ Binding SelectedTrain.Id }">Test text</TextBlock>
</UserControl>
TrainList class:
public partial class TrainList : UserControl
{
public TrainList()
{
this.InitializeComponent();
this.DataContext = this;
}
public Train SelectedTrain { get; set; }
public void SelectionChanged(object sender, SelectionChangedEventArgs e)
{
Debug.Print(this.SelectedTrain.Id);
}
}
Note: The Train class implements INotifyPropertyChanged.
If I got this to work, I'd apply the binding to the trainView user control (not sure if this would work) instead to the text block.
<UserControl>
<customControls:trainView x:Name="trainView" DataContext="{Binding ElementName=trainList, Path=SelectedTrain}"></customControls:trainView>
</UserControl>
And then, I would access that variable someway from the code-behind of trainView.
(And after this, I would like to share a different variable from trainView with its parent user control, but maybe that's another question).
My current question is: could this be done this way or would I need to follow another strategy?
Take this simple view model, with a base class that implements the INotifyPropertyChanged interface, and a Train, TrainViewModel and MainViewModel class.
public class ViewModelBase : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string propertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
protected void SetValue<T>(
ref T storage, T value, [CallerMemberName] string propertyName = null)
{
if (!Equals(storage, value))
{
storage = value;
OnPropertyChanged(propertyName);
}
}
}
public class Train : ViewModelBase
{
private string name;
public string Name
{
get { return name; }
set { SetValue(ref name, value); }
}
private string details;
public string Details
{
get { return details; }
set { SetValue(ref details, value); }
}
// more properties
}
public class TrainViewModel : ViewModelBase
{
public ObservableCollection<Train> Trains { get; }
= new ObservableCollection<Train>();
private Train selectedTrain;
public Train SelectedTrain
{
get { return selectedTrain; }
set { SetValue(ref selectedTrain, value); }
}
}
public class MainViewModel
{
public TrainViewModel TrainViewModel { get; } = new TrainViewModel();
}
which may be initialized in the MainWindow's constructor like this:
public MainWindow()
{
InitializeComponent();
var vm = new MainViewModel();
DataContext = vm;
vm.TrainViewModel.Trains.Add(new Train
{
Name = "Train 1",
Details = "Details of Train 1"
});
vm.TrainViewModel.Trains.Add(new Train
{
Name = "Train 2",
Details = "Details of Train 2"
});
}
The TrainDetails controls would look like this, of course with more elements for more properties of the Train class:
<UserControl ...>
<StackPanel>
<TextBlock Text="{Binding Name}"/>
<TextBlock Text="{Binding Details}"/>
</StackPanel>
</UserControl>
and the parent UserControl like this, where I directly use a ListBox instead of a TrainList control:
<UserControl ...>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<ListBox ItemsSource="{Binding Trains}"
SelectedItem="{Binding SelectedTrain}"
DisplayMemberPath="Name"/>
<local:TrainDetailsControl Grid.Column="1" DataContext="{Binding SelectedTrain}"/>
</Grid>
</UserControl>
It would be instantiated in the MainWindow like this:
<Grid>
<local:TrainControl DataContext="{Binding TrainViewModel}"/>
</Grid>
Note that in this simple example the elements in the UserControls' XAML bind directly to a view model instance that is passed via their DataContext. This means that the UserControl know the view model (or at least their properties). A more general approach is to declare dependency properties in the UserControl class, that are bound to view model properties. The UserControl would then be independent of any particular view model.

Data Binding is not working

i am trying to understand the concept of data binding, i learnt that data binding is the way that you can bind UI elements to objects so that any change in either of them will effect the other(in case of two way binding), but this is not working in this simple example :
my class :
namespace dataBinding.Models
{
public class person
{
public string name { get; set; }
public override string ToString()
{
return this.name;
}
}
}
the XAML :
xmlns:models="using:dataBinding.Models"
<Page.DataContext>
<models:person x:Name="personObject" name="Person's Name"/>
</Page.DataContext>
<TextBlock VerticalAlignment="Center" HorizontalAlignment="Right"
Text="{Binding name, Mode=TwoWay}" />
<Button Name="changeNameButton"
VerticalAlignment="Center"
HorizontalAlignment="Left"
Content="Change Name" />
the C# Code :
namespace dataBinding
{
public sealed partial class MainPage : Page
{
public MainPage()
{
this.InitializeComponent();
changeNameButton.Click += ChangeNameButton_Click;
}
private void ChangeNameButton_Click(object sender, RoutedEventArgs e)
{
personObject.name = "New Name";
}
}
}
when i run the app the TextBlock shows "Person's Name" but clicking the changeName Button doesn't change what is shown in the TextBlock so, what am i missing here ?
Your missing the INotifyPropertyChanged interface implementation. Your XAML does not know it has been changed.
Easy to fix, use https://github.com/Fody/PropertyChanged It will add the code to notify everyone that the property has changed.
[ImplementPropertyChanged]
public class person
{
public string name { get; set; }
public override string ToString()
{
return this.name;
}
}

XAML binding not working in C# UWP app

i have a problem with binding an ObservableCollection in XAML
the class :
[DataContract]
public class Result
{
[DataMember]
public string title { get; set; }
[DataMember]
public string href { get; set; }
[DataMember]
public string ingredients { get; set; }
[DataMember]
public string thumbnail { get; set; }
}
the Observable Collection :
private ObservableCollection<Result> resultTest;
the XAML code for binding :
<ListView Name="RecipeListView"
ItemsSource="{Binding resultTest}">
<ListView.ItemTemplate>
<DataTemplate x:DataType="data:Result">
<StackPanel Orientation="Vertical">
<TextBlock Name="RecipeTitleTextBlock"
Text="{Binding title}"
Foreground="Black"
FontSize="24">
</TextBlock>
<Image Name="RecipeImage"
Source="{Binding thumbnail}"
Width="45"
Height="45">
</Image>
</StackPanel>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
Everything seems to be correct as application launches, but there is no content visible that I was binding to.
Thanks you guys for any help.
The issue is you're declaring "resultTest" as a "filed", not a property.
However, the binding system uses reflection to look for property, it does not look for "field".
Changing your resultTest declaration to property would solve the issue.
public ObservableCollection<Result> resultTest {get; private set;}
Also, make sure you have the DataContext properly set with xaml or code-behind like
this.DataContext = this
or
this.DataContext = new ViewModel()
Depending on what your DataContext really is.
My full MainWindow.cs looks like below, and the ListView binding works.
public sealed partial class MainPage : Page
{
public ObservableCollection<Result> resultTest { get; private set; }
public MainPage()
{
resultTest = new ObservableCollection<Result>();
resultTest.Add(new Result() { title = "Hello" });
resultTest.Add(new Result() { title = "World" });
this.DataContext = this;
this.InitializeComponent();
}
}
If you have your datacontext set (i.e. in your code behind have datacontext=this or if you are using mvvm have your datacontext set in your window like this
<Window.DataContext>
<local:MainViewModel/>
</Window.DataContext>
And if you have your collection property setup like
public ObservableCollection<Result> resultTest {get;set;}
as Szabolcs Desi suggested then I would try removing the x:Datatype="data:Result" on your DataTemplate. I tested the code you have minus that and it works for me.
Sorry for this dumb question , the problem was I was using a static method that returned an ObservableCollection to a variable. I thought I should remove it to a void function and fill the collection by foreach inside. Everything in xaml was right.

How to bind a single object instance in WPF?

I am a WPF newcomer, and I've been searching for two days with no luck. I have a WPF window that has several text box controls, and a single object with some properties. This object is passed to the codebehind of my WPF window in it's constructor:
public partial class SettingsDialog : Window
{
public SettingsObject AppSettings
{
get;
set;
}
public SettingsDialog(SettingsObject settings)
{
this.AppSettings = settings;
InitializeComponent();
}
}
The SettingsObject looks something like this (simplified for clarity):
public class SettingsObject
{
public string Setting1 { get; set; }
public string Setting2 { get; set; }
public string Setting3 { get; set; }
public SettingsObject()
{
this.Setting1 = "ABC";
this.Setting2 = "DEF";
this.Setting3 = "GHI";
}
}
And my WPF window (simplified):
<Window x:Class="MyProgram.SettingsDialog"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
DataContext="{Binding Source=AppSettings}">
<Grid>
<TextBox Name="Setting1Textbox" Text="{Binding Path=Setting1}"></TextBox>
<TextBox Name="Setting2Textbox" Text="{Binding Path=Setting2}"></TextBox>
<TextBox Name="Setting3Textbox" Text="{Binding Path=Setting3}"></TextBox>
</Grid>
</Window>
How do you acheive two-way binding in this situation? I've tried what you see above (and so much more) but nothing works!
Have you set the DataContext property of the window to your instance of AppSettings?
public SettingsDialog(SettingsObject settings)
{
InitializeComponent();
//While this line should work above InitializeComponent,
// it's a good idea to put your code afterwards.
this.AppSettings = settings;
//This hooks up the windows data source to your object.
this.DataContext = settings;
}

Categories

Resources