UWP Binding a specific property of a hierachical object - c#

I want to access some values at the "root" of my main object (which is an object with a hierachical structure) inside some level of my datagrid that have another datacontext. At the moment it looks like this:
see image
As you can see on the screenshot with the textblocks above the grid I can access these properties outside, but in the grid I can't and I get a binding error.
The first level of my grid is binded with value1, the second with value2.
The errors for first level is:
Error: BindingExpression path error: 'TestValue' property not found on 'ProjectSolution.Core.Models.CaseTask'. BindingExpression: Path='TestValue' DataItem='ProjectSolution.Core.Models.CaseTask'; target element is 'Windows.UI.Xaml.Controls.TextBlock' (Name='null'); target property is 'Text' (type 'String')
I have the same error for the second level of my grid with the other property.
Here is the code I have simplified to highlight my problem:
<DataTemplate x:Key="templateCell1" x:DataType="models:Case">
<Grid x:Name="ItemCellGrid">
<TextBlock Text="{Binding TestValue}"></TextBlock>
</Grid>
</DataTemplate>
<DataTemplate x:Key="templateCell2" x:DataType="models:Case">
<Grid x:Name="ItemCellGrid">
<TextBlock Text="{Binding TestValue2}"></TextBlock>
</Grid>
</DataTemplate>
<controls:DataGrid
x:Name="dataGrid"
ItemsSource="{x:Bind ViewModel.SingleCase.CaseTasks}">
<controls:DataGrid.Columns>
<controls:DataGridTextColumn Header="Tasks" Binding="{Binding Name}" FontWeight="SemiBold" />
</controls:DataGrid.Columns>
<controls:DataGrid.RowDetailsTemplate>
<DataTemplate>
<StackPanel Background="{ThemeResource ApplicationPageBackgroundThemeBrush}" >
<Grid>
<controls:DataGrid
x:Name="rowDatagrid"
ItemsSource="{Binding CaseTasksDetails}">
<controls:DataGrid.Columns>
<controls:DataGridTextColumn Header="Sub-Tasks" Binding="{Binding Name}" FontWeight="SemiBold" />
<controls:DataGridTemplateColumn Header="Test" CellTemplate="{StaticResource templateCell2}" />
</controls:DataGrid.Columns>
</controls:DataGrid>
</Grid>
</StackPanel>
<DataTemplate>
</controls:DataGrid.RowDetailsTemplate>
</controls:DataGrid>
My object:
public class Case
{
public string Name { get; set; }
public ObservableCollection<CaseTask> CaseTasks { get; set; }
public string TestValue { get; set; }
public string TestValue2 { get; set; }
public Case()
{
CaseTasks = new ObservableCollection<CaseTask>();
CaseTasks.Add(new CaseTask() { Name = "Level2_1" });
CaseTasks.Add(new CaseTask() { Name = "Level2_2" });
CaseTasks.Add(new CaseTask() { Name = "Level2_3" });
}
}
public class CaseTask
{
public string Name { get; set; }
public ObservableCollection<CaseTaskDetail> CaseTasksDetails { get; set; }
public CaseTask()
{
CaseTasksDetails = new ObservableCollection<CaseTaskDetail>();
CaseTasksDetails.Add(new CaseTaskDetail() { Name = "Level3_1" });
CaseTasksDetails.Add(new CaseTaskDetail() { Name = "Level3_2" });
CaseTasksDetails.Add(new CaseTaskDetail() { Name = "Level3_3" });
}
}
public class CaseTaskDetail
{
public string Name { get; set; }
}
My viewmodel:
public class CaseDetailViewModel
{
private Case _case;
public Case SingleCase
{
get { return _case; }
set {_case =value; }
}
public CaseDetailViewModel()
{
_case = new Case();
_case.TestValue = "Value1";
_case.TestValue2 = "Value2";
}
Of course one of the simplest ways would be to propagate the properties in the child objects, but this would create a lot of redundancy in my data. I am open to any suggestions.

You could save the value of TestValue2 which you want to access inside some level of the DataGrid as a static value of MainPage, and add DataGrid.LoadingRowDetails event handler to change the value of TestValue based on the selected row in the first level of DataGrid. And you could show the value by a data binding with a Converter, like this:
MainPage.xaml.cs
public static string testString = "";
……
private void dataGrid_LoadingRowDetails(object sender, DataGridRowDetailsEventArgs e)
{
int index = dataGrid.SelectedIndex;
testString = (index+2).ToString()+"th row: "+ ViewModel.SingleCase.TestValue2;
}
Converter class
public class DateFormatter : IValueConverter
{
// This converts the DateTime object to the string to display.
public object Convert(object value, Type targetType,
object parameter, string language)
{
return MainPage.testString;
}
// No need to implement converting back on a one-way binding
public object ConvertBack(object value, Type targetType,
object parameter, string language)
{
throw new NotImplementedException();
}
}
MainPage.xaml
<Page.Resources>
<local:DateFormatter x:Key="MyConverter"/>
……
</Page.Resources>
//Use the Converter in a column inside the second level of DataGrid
<controls:DataGridTextColumn Width="120" Header="Test" Binding="{Binding Converter={StaticResource MyConverter}}"/>

Related

WPF MVVM - Combo box inside data grid [duplicate]

This question already has answers here:
Why does WPF support binding to properties of an object, but not fields?
(2 answers)
Closed 8 months ago.
I Have a data grid of objects (CopyObject) , each object contains list of objects (PGroupGridObject) that i want to display inside combo box.
My View Model :
private ObservableCollection<CopyObject> foldersToCopy;
public ObservableCollection<CopyObject> FoldersToCopy
{ get => foldersToCopy; set { foldersToCopy = value; OnPropertyChanged("FoldersToCopy"); } }
CopyObject class :
public class CopyObject
{
public ObservableCollection<PGroupGridObject> pGroups;
public CopyObject(string s , ObservableCollection<PGroupGridObject> pGroups)
{
Name = s;
IsFolder = (File.GetAttributes(Name) & FileAttributes.Directory) == FileAttributes.Directory;
this.pGroups = pGroups;
}
public string Name { get; set; }
}
PGroupGridObject class :
public class PGroupGridObject : ViewModelBase
{
public PGroupGridObject(object o)
{
Object = (DeployTool.Domain.Model.PGroup)o;
PGroupName = Object.Name;
ProductName = Object.Project_Product.Name;
}
private bool isChecked { get; set; }
private string pGroupName;
public string PGroupName
{
get { return pGroupName; }
set
{
pGroupName = value;
OnPropertyChanged("PGroupName");
}
}
public bool IsChecked
{
get { return isChecked; }
set
{
isChecked = value;
OnPropertyChanged("IsChecked");
}
}
}
my xaml :
<DataGrid ItemsSource="{Binding FoldersToCopy}" Grid.Row="7" Grid.Column="1" SelectionUnit="FullRow" RowHeaderWidth="0" SelectedItem="{Binding CopyObjectSelected}"
Style="{StaticResource DataGridStyle}" d:ItemsSource="{d:SampleData ItemCount=2}" AutoGenerateColumns="False"
MinHeight="60" MaxHeight="180" Margin="0,10,0,10"
Foreground="White" Background="#292929" FontSize="16" BorderThickness="0" HorizontalAlignment="Right" MaxWidth="800" MinWidth="600" >
<DataGrid.Columns>
<DataGridTextColumn Header="File Path" Binding="{Binding Name}" CanUserResize="True" MinWidth="520"/>
<DataGridComboBoxColumn Header="PGroups"
ItemsSource="{Binding pGroups, UpdateSourceTrigger=PropertyChanged}"
DisplayMemberPath="{Binding PGroupName}"
/>
</DataGrid.Columns>
</DataGrid>
All the examples I found show Bind from the View Model and not from the object in the grid.
the goal eventually is to display check box inside every group in combo box.
I would appreciate help.
pGroups must be a public property for you to be able to bind to it:
public ObservableCollection<PGroupGridObject> pGroups { get; }
Also, the DisplayMemberPath should be set to a string that matches the name of a property of a PGroupGridObject:
DisplayMemberPath="PGroupName"

WPF TabControl: a dictionary for tab headers

I've got a TabControl that is bound to its viewmodel Items property:
<TabControl ItemsSource="{Binding Items}"/>
This is the viewmodel class:
public class ViewModel
{
public ObservableCollection<object> Items { get; } = new ObservableCollection<object>();
public ViewModel()
{
Items.Add(new Person() { FirstName = "Alan", LastName = "Turing" });
Items.Add(new Car() { ModelName = "Fiesta", Manifacturer = "Ford" });
}
}
Items's type is ObservableCollection<object>; this is because each tab represents a different kind of object, in this example a Person and a Car.
Question
I want to bind the tab's header to a text that is not available as a property of the bound classes, let say a manually provided header.
For example, I would like the first tab to have the text "Person" as header and the second to have the text "Car" as header:
I thought of:
adding the Header property to each class. It's easy and straightforward from the binding point of view, but this is sort of a generalized viewer and not all the interfaces of the input data can be modified.
an "attached property" would still require to act at class-level; yet feasible but probably a bit overkill
A Dictionary<object, string> defined at the viewmodel level, let's call it Headers.
Each time i add an object to the collection, i would also add an Header to the Headers dictionary. Its key would be the object itself.
Person p = new Person() { FirstName = "Alan", LastName = "Turing" };
Headers.Add(p, "Person");
Items.Add(p);
But how to write the binding expression then?
<TabControl ItemsSource="{Binding Items}">
<TabControl.ItemTemplate>
<DataTemplate>
<Label Content="{Binding ????????}"/>
</DataTemplate>
</TabControl.ItemTemplate>
</TabControl>
Even if possible, this solution seems to be not very 'binding-friendly'.
There seems to be two problems here: dynamically attach an information to an instance, and access it via binding.
Any ideas?
Postscript - the code
The code, for completeness.
My classes:
public class Person
{
public string FirstName { get; set; }
public string LastName { get; set; }
}
public class Car
{
public string ModelName { get; set; }
public string Manifacturer { get; set; }
}
My DataTemplate, defined in the <Winodow.Resources> tag.
<DataTemplate DataType="{x:Type local:Person}">
<StackPanel>
<Label Content="{Binding FirstName}"/>
<Label Content="{Binding LastName}"/>
</StackPanel>
</DataTemplate>
<DataTemplate DataType="{x:Type local:Car}">
<StackPanel>
<Label Content="{Binding Manifacturer}"/>
<Label Content="{Binding ModelName}"/>
</StackPanel>
</DataTemplate>
You can also override the ToString() method for your model classed and return your friendly name from there.
public class Person
{
public string FirstName { get; set; }
public string LastName { get; set; }
public override string ToString()
{
return "Person";
}
}
Template
<TabControl ItemsSource="{Binding Items}">
<TabControl.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding}"/>
</DataTemplate>
</TabControl.ItemTemplate>
</TabControl>

How to do Data Binding for Textblock within a LongListselector in windows phone app?

Hi, I am trying to bind the data for text block within a LongListSelector. But I am not getting any Output for it, kindly help me.
This is my XAML code:
<phone:LongListSelector ItemsSource="{Binding ''}" x:Name="longListSelector" HorizontalAlignment="Left" Height="680" VerticalAlignment="Top" Width="446" >
<phone:LongListSelector.ItemTemplate>
<DataTemplate>
<StackPanel>
<TextBlock Name="name" Text="{Binding DataContext.TextContent,ElementName=page,Mode=OneWay}" Height="100" Width="100" HorizontalAlignment="Center">
</TextBlock>
</StackPanel>
</DataTemplate>
</phone:LongListSelector.ItemTemplate>
</phone:LongListSelector>
In the C# code I have parsed data which i need to display in the windows phone, in a menu format.
Part of C# code is shown below:
XDocument document = XDocument.Parse(e.Result);
var data1 = from query in document.Descendants("location")
select new Data
{
Lat = (string)query.Element("lat"),
Lag = (string)query.Element("lng")
};
foreach (var d in data1)
{
JsonParsing(d.Lat, d.Lag);
}
data1 = from query in document.Descendants("result")
select new Data
{
Country = (string)query.Element("formatted_address")
};
foreach (var d in data1)
{
// ob.JsonParsing(d.Lat, d.Lag);
//XmlParsing(d.Lat, d.Lag);
val = d.Country;
//listbox.Items.Add(val);
//StringsList.Add(val);
TextContent=val;
I want the value of the country to be shown inside the textblock, kindly help me figure this out as I am pretty new to this field, thanks.
try like this
a good reference
<DataTemplate>
<StackPanel VerticalAlignment="Top">
<TextBlock Text="{Binding Value}" />
</StackPanel>
</LongListSelector>
CodeBehind
**Add a public property only public property can be participate in databinding**
#region Public Properties
private ObservableCollection<YourModel> _collectionofValue;
public ObservableCollection<YourModel> CollectionofValues
{
get
{
return _collectionofValue;
}
set
{
_collectionofValue=value;
raisepropertyChanged("CollectionofValues");
}
}
private string _value;
public string Value
{
get
{
return _errorMessage;
}
set
{
_errorMessage = value;
RaisePropertyChanged("Value");
}
}
#endregion
**Set value to this public property when you get value**
// for single values
public void getValue()
{
value =GetXmlValue(); // your method that will return the value;
}
// as it is a collection
public void getValuestoCollection()
{
Collection.Add(new YourModel(value="SampleValue1");
Collection.Add(new YourModel(value="SampleValue1");
Collection.Add(new YourModel(value="SampleValue1");
Collection.Add(new YourModel(value="SampleValue1");
}
YourModel
// the collection of this model is binded to the LongListSelector.
public class ModelName
{
public string Values {get;set;}
}
reference
<phone:LongListSelector ItemsSource="{Binding Items}" x:Name="longListSelector" HorizontalAlignment="Left" Height="680" VerticalAlignment="Top" Width="446" >
<phone:LongListSelector.ItemTemplate>
<DataTemplate>
<StackPanel>
<TextBlock Name="name" Text="{Binding Path=TextContent}" Height="100" Width="100" HorizontalAlignment="Center">
</TextBlock>
</StackPanel>
</DataTemplate>
</phone:LongListSelector.ItemTemplate>
</phone:LongListSelector>
Your C# algm should be:
i) Have a viewmodel class
public class MyViewModel
{
public ObservableCollection<MyDataItem> Items {get; set;}
public MyViewModel()
{
Items=new ObservableCollection<MyDataItem>();
loop //add your items to your 'Items' property so that you can bind this with LongListSelector ItemsSource
{
Items.Add(new MyDataItem("mystring"));
}
}
}
public class MyDataItem
{
public MyDataItem(string s)
{
TextContent=s;
}
public string TextContent {get;set;}
}
ii) Create an instance to ViewModel class and set DataContext
// write this in the constructor of the page which contains the LongListSelector
public MyViewModel vm;
constructor()
{
vm=new MyViewModel();
this.DataContext=vm;
}

Add DataGrid checkboxcolumn with different values in different rows

I got a DataGrid that is bound to an object PlacementData (PD). PD has a property "P_Unit".
public class PlacementData
{
public bool PIsChecked { get; set; }
public string PlacementHeader { get; set; }
public string P_NumberOfCases { get; set; }
public int P_Value1 { get; set; }
public int P_Value2 { get; set; }
public int P_Value3 { get; set; }
public int P_Value4 { get; set; }
public int P_Value5 { get; set; }
public string P_Unit { get; set; }
}
In my DataGrid I added a Combobox in DataTemplateColumn.
<DataGridTemplateColumn x:Name="UnitColumn1" Header="Unit" MinWidth="80" >
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<ComboBox Text="{Binding P_Unit}">
<ComboBoxItem Content="kg/m3" IsSelected="True"/>
<ComboBoxItem Content="gm/cm3"/>
</ComboBox>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
On start of the window, I set the itemsource with 4 rows with headers added.
private List<PlacementData> datagrid1CollectionData()
{
List<PlacementData> authors = new List<PlacementData>();
authors.Add(new PlacementData()
{
PlacementHeader = "Based On Injection Rate",
});
authors.Add(new PlacementData()
{
PlacementHeader = "Based On Viscosity"
});
authors.Add(new PlacementData()
{
PlacementHeader = "Based On Sheer Thinning"
});
authors.Add(new PlacementData()
{
PlacementHeader = "k"
});
return authors;
}
dataGrid1.ItemsSource = datagrid1CollectionData();
My each row need different values for Unit combo box. For eg., 1 row needs "kg, gm", 2nd needs "meter, cm, feet", 3rd needs "ltr, ml, ton", & 4th needs it to be blank.
How do I set these values ? I think on each row creation, I can create a List and assign that as itemsource to the checkbox. But how is this possible in the above code. Checkbox Itemsource for each row of checkbox ???
I would recommend to use EditCellTemplate but it is up to you and task requirements.
In the combobox in the DataTemplate use custom IValueConverter (I have used PlacementHeader as dependand property, you can use what actually needed or PlacementData itself):
<ComboBox SelectedValue ="{Binding P_Unit}" ItemsSource="{Binding PlacementHeader, Converter={StaticResource DependedValuesConverter}}">
</ComboBox>
and some sample of converter just like idea:
public class DynamicValuesConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if (value != null)
switch (value.ToString())
{
case "Based On Injection Rate":
return new[] { "kg/m3", "gm/cm3" };
case "Based On Viscosity":
return new[] { "some other..." };
}
return new string[0];
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
To implement multi selection on combobox you can use some open source CheckComboBox.
EDIT
According to your comment: you can add converter anywhere where it is visible to data template I have added directly to datatemplate just to demonstrate:
<DataGridTemplateColumn.CellEditingTemplate>
<DataTemplate>
<DataTemplate.Resources>
<local:DynamicValuesConverter x:Key="DependedValuesConverter" />
</DataTemplate.Resources>
<ComboBox SelectedValue="{Binding P_Unit}" ItemsSource="{Binding PlacementHeader, Converter={StaticResource DependedValuesConverter}}"></ComboBox>
</DataTemplate>
</DataGridTemplateColumn.CellEditingTemplate>
"local" has to point to your DynamicValuesConverter namesapce.

Problem with DataBinding in mvvm

I have big problem with databinding.
I cant bind data to children control. I'm really newbie in MVVM and I spend a lot of hours at this example and I have no idea what is wrong with this Code.
Little explanation:
I have MainWindow. It has UserControl to display list of todo.
I want to set my MyWindow class ParentViewModel as DataContext.
DataContext has TodoItemModelView as subdatacontext which must be datacontext of UserControlTodoItems.
<Window x:Class="Repo.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:Repo="clr-namespace:Repo" Title="Window1" Height="300" Width="300">
<Window.Resources>
<Repo:ParentViewModel x:Key="parentVM"/>
</Window.Resources>
<Window.DataContext>
<StaticResourceExtension ResourceKey="parentVM"/>
</Window.DataContext>
<Grid>
<Repo:UserControlTodoItems DataContext="{Binding Path=todoItemModelView}">
</Repo:UserControlTodoItems>
</Grid>
</Window>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
class ParentViewModel
{
public TodoItemModelView todoItemModelView { get; set; }
public ParentViewModel()
{
this.todoItemModelView=new TodoItemModelView();
}
}
public class TodoItemModelView
{
public ObservableCollection<TodoItem> todoItems { get; set; }
public TodoItemModelView()
{
ObservableCollection<TodoItem> loadedTodoItems = new ObservableCollection<TodoItem>();
loadedTodoItems.Add(new TodoItem() { Code = "10", ObjectCode = "DE", ObjectType = ObjectType.Country, Status = TodoItemStatus.InProgress, Type = TodoItemType.CollectPhotos });
loadedTodoItems.Add(new TodoItem() { Code = "11", ObjectCode = "DE", ObjectType = ObjectType.Country, Status = TodoItemStatus.Todo, Type = TodoItemType.DescribeOjbect });
loadedTodoItems.Add(new TodoItem() { Code = "12", ObjectCode = "DE", ObjectType = ObjectType.Country, Status = TodoItemStatus.Accomplshed, Type = TodoItemType.CollectVideos });
todoItems = loadedTodoItems;
}
}
<UserControl x:Class="Repo.UserControlTodoItems"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:Repo="clr-namespace:Repo" Height="auto" Width="auto">
<UserControl.Resources>
<Repo:TodoItemStatusConverter x:Key="TodoItemStatusConverter"/>
</UserControl.Resources>
<Grid>
<ListBox ItemsSource="{Binding Path=todoItems}" Name="lbTasks">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<CheckBox IsChecked="{Binding Path=Status, Converter={StaticResource TodoItemStatusConverter}}"/>
<TextBlock Text="{Binding Path=Type}" />
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</Grid>
</UserControl>
public UserControlTodoItems()
{
InitializeComponent();
}
I correct this.
I must add one question:
is there any simple way to inform parentmodel, of change checkbox at listbox?
this is a converter:
public class TodoItemStatusConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
TodoItemStatus todoItemStatus = (TodoItemStatus)value;
if (todoItemStatus == TodoItemStatus.Accomplshed)
{
return true;
}
else
{
return false;
}
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
if ((bool) value)
{
return TodoItemStatus.Accomplshed;
}
else
{
return TodoItemStatus.InProgress;
}
}
This is class TodoItem:
public class TodoItem
{
public TodoItemType Type { get; set; }
public TodoItemStatus Status { get; set; }
public string Code { get; set; }
public string ObjectCode { get; set; }
public ObjectType ObjectType { get; set; }
}
Why is the binding for your "lbTasks" Listbox just "{Binding}" and not "{Binding Path=todoItems}"
I'm really taking a quick glance at your code here.. you seem to be passing the todoItemModelView as a DataContext properly, but never inform the listbox where in that data context it will find its items.
You may also want to use an ObservableCollection for the list in the VM so you can add and remove todo's in a way the GUI can respond to
<CheckBox IsChecked="{Binding Path=Status, Converter={StaticResource TodoItemStatusConverter}}"/>
This implies there is a property on ToDoItemViewModel called Status - but there isn't! Rethink your ToDoItemVm class to just be a wrapper for a toDoItem (ie, public ToDoItemVm(ToDoItem model) and get that array of items into the PArentVm (do use ObservableCollection and bind it to the list box. Also add a SelectedToDoItem property on the ParentVm. So your binding for the list box includes something like
ItemsSource="{Binding ToDoTems}"
IsSynchronizedWithCurrentItem="True"
SelectedItem="{Binding SelectedToDoItem, Mode=TwoWay}"
Then expose that Status property on your ToDoItemVm, have the class implement INPC, and raise PropertyChanged in the setter.
It may take some work to sort it out, so feel free to ask more questions as you go. The converter idea is fine.
HTH,
Berryl

Categories