Binding list property to TextBlock - wpf

I have a class named MyClass which looks like this:
public class MyClass : BaseObject, IDisposable
{
public int MyClassId { get; set; }
public IList<MyClassTranslation> MyTranslations { get; set; }
}
And MyClassTranslation looks like this:
public class MyClassTranslation
{
public string MyClassName { get; set; }
}
I have a view and a view model. The View model looks like this:
class MyClassViewModel : ViewModelBase, IMyClassViewModel
{
public MyClassViewModel()
{
myObjects = GetMyObjects();
}
public IList<MyClass> myObjects { get; set; }
}
And here's the view:
<ItemsControl ItemsSource="{Binding myObjects}" >
<ItemsControl.ItemTemplate>
<DataTemplate>
<StackPanel HorizontalAlignment="Center">
<TextBlock FontSize="24pt" FontWeight="Bold" TextAlignment="Center" Text="{Binding MyClassTranslation.MyClassName[0]}"></TextBlock>
</StackPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
The problem is that the myClassName property isn't shown. How do I bind it?

assuming that the data context is set properly, so the proper binding would be
<StackPanel HorizontalAlignment="Center">
<TextBlock FontSize="24pt"
FontWeight="Bold"
TextAlignment="Center"
Text="{Binding MyTranslations[0].MyClassName}">
</TextBlock>
</StackPanel>
you can use indexer to bind such values

Related

How to get the SelectedItem of several datagrids?

Foreach treeview-item i got an own datagrid. Treeview-items and datagrids are filled by binding.
On textboxes i got a binding to the selected item of the datagrids. But the binding on these textboxes only works with the first datagrid. Every other datagrid doesn't transfer the selecteditem to the textboxes:
Here is the treeview with the datagrid:
<TreeView ItemsSource="{Binding Path=PlaceList}">
<TreeView.ItemTemplate>
<DataTemplate>
<TreeViewItem Header="{Binding Path=Name}">
<DataGrid ItemsSource="{Binding MachinesInPlace, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
SelectionUnit="FullRow"
SelectedItem="{Binding SelectedMachine, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
AutoGenerateColumns="True"
IsSynchronizedWithCurrentItem="True"
SelectionMode="Single">
</DataGrid>
</TreeViewItem>
</DataTemplate>
</TreeView.ItemTemplate>
</TreeView>
Here is the textbox:
<TextBox Text="{Binding PlaceList/SelectedMachine.Name, ValidatesOnDataErrors=True}" />
I am working with MvvmLight. My ViewModel holds the PlaceList:
public ObservableCollection<PlaceModel> PlaceList { get; set; } = new ObservableCollection<PlaceModel>();
public ObjectInspectorViewModel()
{
PlaceList = PlaceModel.GetPlaces(BaseResourcePaths.PlacesCsv);
}
That s my place-model:
public class PlaceModel
{
public int Id { get; set; }
public string Name { get; set; } = "_CurrentObjectName";
public string Length { get; set; }
public string Width { get; set; }
public string Height { get; set; }
public ObservableCollection<MachineModel> MachinesInPlace { get; set; }
public MachineModel SelectedMachine { get; set; }
public static ObservableCollection<PlaceModel> GetPlaces(string filepath)
{
[...]
}
}
I tried something out but at last i dont know how to fix the bug. What s the problem? My suggestion is the property ''SelectedMachine'' inside the place-model...
Here is an example-project (with the additional solution of Sebastian Richter). It shows the problems: https://www.file-upload.net/download-12370581/DatagridTreeViewError.zip.html
I'm quiet sure you forget to implement INotifyPropertyChanged in you class PlaceModel. The problem is after you changed the selection, the Property Placemodel.SelectedMachine will be updated but no event will be fired to populate this change in the View.
Because you use MVVM Light you can derive from ObservableObject which already implements this Interface.
So change your PlaceModel to following code:
public class PlaceModel : ObservableObject
{
private MachineModel _selectedMachine;
public int Id { get; set; }
public string Name { get; set; } = "_CurrentObjectName";
public string Length { get; set; }
public string Width { get; set; }
public string Height { get; set; }
public ObservableCollection<MachineModel> MachinesInPlace { get; set; }
public MachineModel SelectedMachine
{
get
{
return _selectedMachine;
}
set
{
// raises Event PropertyChanged after setting value
Set(ref _selectedMachine, value);
}
}
public static ObservableCollection<PlaceModel> GetPlaces(string filepath)
{
[...]
}
Edit:
I guess the binding doesn't know which element to bind to from your ObservableCollection (many to one relation) because you set it as the reference in your TextBox.
So try to remove the SelectedMachine property from the Model and add it back to the ViewModel:
class ViewModel : ViewModelBase
{
...
private MachineModel _selectedMachine;
public MachineModel SelectedMachine
{
get
{
return _selectedMachine;
}
set
{
// raises Event PropertyChanged after setting value
Set(ref _selectedMachine, value);
}
}
...
}
Also change your XAML to following code (I used your example project):
<Grid x:Name="LayoutRoot">
<Grid.RowDefinitions>
<RowDefinition Height="2*"></RowDefinition>
<RowDefinition></RowDefinition>
</Grid.RowDefinitions>
<!-- Row #1 -->
<Grid>
<!-- TreeView und DataGrids-->
<TreeView ItemsSource="{Binding Path=PlaceList}">
<TreeView.ItemTemplate>
<DataTemplate>
<TreeViewItem Header="{Binding Path=Name}">
<DataGrid ItemsSource="{Binding MachinesInPlace, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
SelectedItem="{Binding DataContext.SelectedMachine, RelativeSource={RelativeSource AncestorType=Window},Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />
</TreeViewItem>
</DataTemplate>
</TreeView.ItemTemplate>
</TreeView>
</Grid>
<!-- Row #2 -->
<Grid Grid.Row="1">
<Grid.ColumnDefinitions>
<ColumnDefinition></ColumnDefinition>
<ColumnDefinition Width="2*"></ColumnDefinition>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition></RowDefinition>
<RowDefinition></RowDefinition>
</Grid.RowDefinitions>
<Label Grid.Row="0"
Content="ID" />
<!-- Textboxen aktualisieren nur bei Auswahl der ersten Datagrid -->
<TextBox Grid.Column="2"
Grid.Row="0"
Text="{Binding SelectedMachine.Id, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />
<Label Grid.Row="1"
Content="Name" />
<TextBox Grid.Column="2"
Grid.Row="1"
Text="{Binding SelectedMachine.Name, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />
</Grid>
</Grid>
The key was to set the correct DataContext for SelectedItem. For this i used following XAML code:
<DataGrid ItemsSource="{Binding MachinesInPlace, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
SelectedItem="{Binding DataContext.SelectedMachine, RelativeSource={RelativeSource AncestorType=Window},Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />
With this the your example project updates the TextBoxes correctly.

XAML Binding to child collection

In a Windows UWP project I'm trying to bind to the following properties in this class
using System;
using System.Collections.ObjectModel;
namespace IAmOkShared.Models
{
public class Client
{
public Guid clientId { get; set; }
public string lastname { get; set; }
public DateTime timestamp { get; set; }
//- List af addresses of this client
public ObservableCollection<Address> clientaddresses;
public Client ()
{
clientId = Guid.Empty;
lastname = string.Empty;
timestamp = DateTime.Today;
clientaddresses = new ObservableCollection<Address>();
}
}
}
Binding to clientId and lastname is no problem, but can't get it right to bind to one or more of the properties of clientaddresses (e.g city, country)
My XAML:
<DataTemplate x:Name="DetailTemplate" x:DataType="models:Client">
<StackPanel Orientation="Vertical">
<StackPanel Orientation="Horizontal">
<TextBlock x:Name="LastNameTextBlock" Text="{Binding lastname}" />
</StackPanel>
<StackPanel Orientation="Vertical">
<TextBlock x:Name="AddressTextBlock" Text="{Binding clientaddresses[0].city}" />
</StackPanel>
</StackPanel>
</DataTemplate>
Any idea how to solve this?
Steven
You are binding to a field instead of a property.
public ObservableCollection<Address> clientaddresses;
Change this to
public ObservableCollection<Address> Clientaddresses { get; private set; }
So it cannot be instantiated outside the viewmodel then the binding should work.
Also you could create additional data template for the Address and just use the entire collection in your datatemplate of the Client, because then you would not get possible Index out of bounds exception if your ClientAddresses collection is empty.
<DataTemplate x:DataType="models:Address">
<TextBlock x:Name="AddressTextBlock" Text="{Binding city}" />
<DataTemplate>
<DataTemplate x:Name="DetailTemplate" x:DataType="models:Client">
<StackPanel Orientation="Vertical">
<StackPanel Orientation="Horizontal">
<TextBlock x:Name="LastNameTextBlock" Text="{Binding lastname}" />
</StackPanel>
<StackPanel Orientation="Vertical">
<ItemsControl ItemsSource="{Binding ClientAddresses}"/>
</StackPanel>
</StackPanel>
</DataTemplate>
Also note your model is not implementing the INotifyPropertyChanged so your UI will not be updated when the model properties change.
Also the convention for back-end private fields is to start with lower case character and for the properties that utilize the INotifyPropertyChanged you should start the property with upper case.
private int myProperty;
public int MyProperty { get { ... } set { ... }}
that's why we create ViewModel and additional property in it
public Address ClientFirstAddress
{
get {return clientaddresses[0].city;}
}
and then Bind it to View,
remember to call NofityPropertyChanged for this property when you set clientaddresses collection

WPF DataGrid ContentControll bindng

I want to make DataGrid with a structure as in image below.
To have two TextBoxes in each cell.
Ive made Class
public class ComplexTable : ViewModelBase
{
public ComplexTable()
{
FirstProperty = new FirstClass();
SecondProperty = new Second();
}
public class FirstClass
{
public FirstClass()
{
First = "FirstString";
Second = "SecondString";
}
public string First { get; set; }
public string Second { get; set; }
}
public class Second
{
public Second()
{
Third = "ThirdString";
Fourth = "FourthString";
}
public string Third { get; set; }
public string Fourth { get; set; }
}
public FirstClass FirstProperty { get; set; }
public Second SecondProperty { get; set; }
}
public ObservableCollection<ComplexTable> _testCollection = new ObservableCollection<ComplexTable>();
private ObservableCollection<ComplexTable> TestCollection
{
get { return _testCollection; }
set
{
_testCollection = value;
RaisePropertyChanged("TestCollection");
}
}
And a TestCollection that should be a ItemsSource for DataGrid.
My DataGrid
<DataGrid CanUserAddRows="True"
ItemsSource="{Binding TestCollection}">
<DataGrid.Columns>
<DataGridTemplateColumn Header="First Column">
<DataGridTemplateColumn.CellEditingTemplate >
<DataTemplate >
<ContentControl>
<StackPanel>
<TextBox Text=" "/>
<TextBox Text=" "/>
</StackPanel>
</ContentControl>
</DataTemplate>
</DataGridTemplateColumn.CellEditingTemplate>
</DataGridTemplateColumn>
<DataGridTemplateColumn Header="Second Column">
<DataGridTemplateColumn.CellEditingTemplate >
<DataTemplate >
<ContentControl>
<StackPanel>
<TextBox Text=" "/>
<TextBox Text=" "/>
</StackPanel>
</ContentControl>
</DataTemplate>
</DataGridTemplateColumn.CellEditingTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
</DataGrid>
I can't figure out how to bind those textboxes. Or i went in a wrong direction?
The datagrid sets the DataContext of the columns to the elements of the ItemSource. Every element in that ItemSource will be displayed as one Row. In your case the ItemSource is TestCollection. Therefore the DataContext inside your DataGridTemplateColumn is set to the elements of the TestCollection. If TestCollection contains ComplexTable elements. You can bind directly to the properties on ComplexTable.
var TestCollection = new ObservableCollection<DataForOneRow> {DataForFirstRow, DataForSecondRow, DataForThirdRow};
public class DataForOneRow {
public string DataForFirstColumnFirstTextBox {get; set;} //left out raise of PropertyChanged for brevity
public string DataForFirstColumnSecondTextBox {get; set;}
public string DataForSecondColumnFirstTextBox {get; set;}
public string DataForSecondColumnSecondTextBox {get; set;}
}
<DataGridTemplateColumn Header="First Column">
<DataGridTemplateColumn.CellEditingTemplate >
<DataTemplate >
<ContentControl>
<StackPanel>
<TextBox Text="{Bidning DataForFirstColumnFirstTextBox}"/>
<TextBox Text="{Bidning DataForFirstColumnSecondTextBox}"/>
</StackPanel>
</ContentControl>
</DataTemplate>
</DataGridTemplateColumn.CellEditingTemplate>
</DataGridTemplateColumn>
FYI: You have to raise a PropertyChanged event in all ViewModel properties.

Wpf adding stack panels from view

I'm not sure my title is clear (poor wpf skills).
What i'm trying to do is to create a smart data entry form. My goal is to have a hard coded data that the user should enter, and on demand (a plus button) he can enter another set of data, every time the user will click the plus button another set will appear in the window (endless)
Edit:
For more details, for a very simple example of what i'm trying to achieve, lets say that this is the window:
And after the user will click the plus button the window will look like this:
And the plus button will always let the user adding more peoples.
Seems like all you need is a List and a ItemControl:
Your Model:
public class User
{
public String Name { get; set; }
public int Age { get; set; }
}
In your ViewModel:
public List<User> Users { get; set; }
//In your constructor
Users = new List<User>();
In your View:
<ItemsControl ItemsSource={Binding Users}>
<ItemsControl.ItemTemplate>
<DataTemplate>
<StackPanel>
<StackPanel Orientation="Horizontal" Margin="10">
<TextBlock Text="Name:" />
<TextBox Text="{Binding Name}" />
</StackPanel>
<StackPanel Orientation="Horizontal" Margin="10">
<TextBlock Text="Age:" />
<TextBox Text="{Binding Age}" />
</StackPanel>
</StackPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
And then below this wire up your add button to a command to point to a method that would do someething like:
private void AddUser()
{
Users.Add(new User());
NotifyPropertyChange("Users");
}
Use an ItemsControl with its ItemsSource property bound to a ReadOnlyObservableCollection<Person>, where Person is a class holding the name and age as strings.
(1) Create Person
public class Person
{
public string Name { get; set; }
public int Age { get; set; }
}
(2) Create PeopleViewModel, holding your collection.
public class PeopleViewModel
{
private ObservableCollection<Person> _people;
public ReadOnlyObservableCollection<Person> People { get; private set; }
public PeopleViewModel()
{
_people = new ObservableCollection<Person>();
People = new ReadOnlyObservableCollection<Person>(_people);
addPerson(); // adding the 1st person
}
// You also need to hook this up to the button press somehow
private void addPerson()
{
_people.Add(new Person());
}
}
(3) Set the DataContext of your window to be a PersonViewModel in the code-behind
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
DataContext = new PeopleViewModel();
}
}
(4) Create an ItemsControl along with a DataTemplate for Person
<ItemsControl ItemsSource="{Binding People}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<StackPanel>
<StackPanel Orientation="Horizontal">
<TextBlock Text="name:" />
<TextBox Text="{Binding Name}" />
</StackPanel>
<StackPanel Orientation="Horizontal">
<TextBlock Text="age:" />
<TextBox Text="{Binding Age}" />
</StackPanel>
</StackPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
Don't forget to hook up your button either through a Command or through the Button.Click event.

WPF TabControl ContentTemplate List

Inexperienced with WPF, so I need a little help. Appreciate the help in advance.
I have the following class:
public class TabDefn
{
public TabDefn() { }
public TabDefn(string inFolderName, List<FilesFolder> inFilesFolders)
{
folderName = inFolderName;
FFs = inFilesFolders;
}
public string folderName { get; set; }
public List<FilesFolder> FFs {get; set;}
}
public class FilesFolder
{
public FilesFolder() {}
//public Image image { get; set; }
public string ffName { get; set; }
//public Image arrow { get; set; }
}
The TabControl.ItemContent is working fine. I can't get anything to show up for the TabControl.ContentTemplate. I've tried many things, but this is where the WPF is right now:
<TabControl Grid.Column="1" Grid.Row="1" Visibility="Hidden" Name="Actions">
<!-- This displays the tab heading perfectly.-->
<TabControl.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding folderName}" />
</DataTemplate>
</TabControl.ItemTemplate>
<!-- This is the content of the tab that I can't get anything to show up in.-->
<TabControl.ContentTemplate>
<DataTemplate>
<ListBox ItemsSource="{Binding FF}">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBox Text="{Binding ffName}" />
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</DataTemplate>
</TabControl.ContentTemplate>
</TabControl>
I don't care if the content changes, so I don't need the INotifyPropertyChanged or ObservableCollection. However, if I have to put all that code in, so be it.
You declare FF as field which is invalid binding source. You need to convert it into property
public List<FilesFolders> FF { get; set; }
and initialize it for example in TabDefn constructor. You can find more as to what is a valid binding source on MSDN

Resources