How to correctly bound to a class with inheritance - c#

I have a treeview. In the treeview I have Student - Homework
The Homework is a Class with Hineritance, so under Homerwork I have -> ScienceH, PhysicsH, MathH
How can I create a XAML that is bound to the Homework list but changes depending on the "child" specifics?
I'm blocked as I do not understand how to do the binding
class Student
{
public string Name;
List<Homework> homeworks;
}
class Homework
{
public DateTime Date;
public int Vote;
}
class ScienceH : Homework
{
public string Topic;
}
class PhysicsH : Homework
{
public int Points;
}
I expect to have an item that shows the additional fields depending on which child they have

You can define a TreeView in UWP XAML like this, as detailed here:
<TreeView ItemsSource="{x:Bind ListOfStudents}">
<TreeView.ItemTemplate>
<DataTemplate x:DataType="model:Student">
<TreeViewItem ItemsSource="{x:Bind homeworks}"
Content="{x:Bind Name}"/>
</DataTemplate>
</TreeView.ItemTemplate>
</TreeView>
You would then need to define a DataTemplate for each of class you expect to display. Add this in your TreeView XAML:
<TreeView.Resources>
<DataTemplate x:DataType="model:ScienceH">
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Date}" />
<TextBlock Text="{Binding Vote}" />
</StackPanel>
</DataTemplate>
<DataTemplate x:DataType="model:PhysicsH ">
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Date}" />
<TextBlock Text="{Binding Vote}" />
<TextBlock Text="{Binding Points}" />
</StackPanel>
</DataTemplate>
</TreeView.Resources>
You can also read more on the DataTemplate class here.
Note that, as suggested in the comments, you need to implement change notifications in your classes to ensure correct binding. This is all detailed here.
Alternatively, you seem to have a TreeView with a tree structure of known, fixed depth: depth 0 would be the list of Student and depth 1 would be the list of Homework, so you could also do without TreeView and simply nest two ItemsControl within one another.

Related

WPF TreeView - If I use binding, the ToString() is never called and the text is blank. If I set the items programmatically, it works fine

The Classes
public class TreeDiagram : INotifyPropertyChanged
{
public string CategoryName
{...}
public ObservableCollection<ImageInfo> DiagramsTRV
{...}
public class ImageInfo : INotifyPropertyChanged
{
public string Category
{ ...}
public override string ToString()
{
return DisplayName;
}
public string DisplayName
{...}
public ObservableCollection<TreeDiagram> TreeDiagrams {...}
<StackPanel Orientation="Vertical">
<StackPanel Orientation="Horizontal">
<StackPanel Orientation="Vertical">
<TextBlock Text="Binding"/>
<TreeView x:Name="trvDiagrams" Width="300" Height="400" ItemsSource="{Binding TreeDiagrams}" HorizontalAlignment="Left">
<TreeView.ItemTemplate>
<HierarchicalDataTemplate DataType="x:Type local:TreeDiagram" ItemsSource="{Binding DiagramsTRV, UpdateSourceTrigger=PropertyChanged}">
<TextBlock Width="250" Text="{Binding CategoryName, UpdateSourceTrigger=PropertyChanged}"/>
</HierarchicalDataTemplate>
</TreeView.ItemTemplate>
</TreeView>
</StackPanel>
<StackPanel Orientation="Vertical">
<TextBox Text="No Binding"/>
<TreeView x:Name="trvNoBind" Width="300" Height="400" HorizontalAlignment="Right">
</TreeView>
</StackPanel>
</StackPanel>
</StackPanel>
If I use binding, the Category Name is displayed, the The TreeDiagram ToString() is never called and the dropdown items are blank.
If I load the tree programmatically with the same data - everything is fine.
But for a variety of reasons, I need to use binding.
I have tried an IValueConverter to take the collection of objects and return a collection of strings - no joy.
Any suggestions as to what I am doing wrong?
Your template is incomplete. You must define a template for the child nodes too. You must either set the HierarchicalDataTemplate.ItemsTemplate property accordingly or define an implicit template e.g. in the ResourceDictionary of the TreeView.
You want to display a recursive or hierarchical data structure. Since your structure consists of different data types, you must define a template for each level.
Note that UpdateSourceTrigger.PropertyChanged is the default value of Binding.UpdateSourceTrigger and is therefore not explicitly required.
<TreeView>
<TreeView.Resources>
<!--
Because 'ImageInfo' has no children,
define a 'DataTemplate' (instaed of another 'HierarchicalDataTemplate').
If this 'DataTemplate' wasn't implicit, you would have to explicitly assign it
to the parent's 'HierarchicalDataTemplate.ItemTemplate' property.
-->
<DataTemplate DataType="{x:Type local:TreeDiagram}">
<TextBlock Text="{Binding}" /> <== Force the call to 'object.ToString' by binding to the object itself
</DataTemplate>
<HierarchicalDataTemplate DataType="{x:Type local:TreeDiagram}"
ItemsSource="{Binding DiagramsTRV}">
<TextBlock Text="{Binding CategoryName}" />
</HierarchicalDataTemplate>
</TreeView.Resources>
</TreeView>

WPF BitmapImage not displaying

I have a WPF project which uses, as best I can, the MVVM pattern. I'm using Visual Studio 2010, and the language behind is C#.
So I have a treeview. The treeview looks like this:
<TreeView Grid.Column="0" Width="Auto" Background="Aquamarine" ItemsSource="{Binding Scenes}" SelectedItemChanged="TreeView_SelectedItemChanged">
<TreeView.DataContext>
<viewModels:ScenesViewModel />
</TreeView.DataContext>
<TreeView.ItemTemplate>
<HierarchicalDataTemplate ItemsSource="{Binding Characters}">
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding SceneName}"></TextBlock>
<Image Source="{StaticResource ImgWorld}" Margin="0,0,5,0" Width="32" Height="32"/>
</StackPanel>
<HierarchicalDataTemplate.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding FirstName}"></TextBlock>
<Border BorderThickness="1" Background="RoyalBlue">
<Image Source="{Binding ImgIcon}" Margin="2"/>
</Border>
</StackPanel>
</DataTemplate>
</HierarchicalDataTemplate.ItemTemplate>
</HierarchicalDataTemplate>
</TreeView.ItemTemplate>
</TreeView>
The point of contention is the Image, the source of which is bound to something called 'ImgIcon'. This is a property of a view model called 'CharacterViewModel'. For some reason I am getting nothing displayed where the image should be.
The most obvious issue might be regarding the data context or whether the MVVM side of things is working. I am fairly sure it is, as there is a TextBlock next to the Image which is also bound to a property called 'FirstName'. This binding works fine.
The two properties are, of course, in the view model, and can be seen here:
public String FirstName
{
get { return Character.FirstName; }
set
{
if (Character.FirstName != value)
{
Character.FirstName = value;
RaisePropertyChanged("FirstName");
}
}
}
private BitmapImage _imgIcon = null;
public BitmapImage ImgIcon
{
get { return _imgIcon; }
set
{
_imgIcon = value;
RaisePropertyChanged("ImgIcon");
}
}
They are slightly different in that the latter does not wrap around a property in the Character class.
So the next possible fault would be the loading of the image. This is accomplished by this method (which at the moment just has a test path for a known working file):
public bool LoadIcon()
{
if (ImgIcon == null)
{
Uri outUri;
if (Uri.TryCreate(#"D:\Pictures\Icons\HCAria01.png", UriKind.Absolute, out outUri) )
{
}
ImgIcon = new BitmapImage(outUri);
return true;
}
else
return false;
}
I put a break point in at 'ImgIcon = new BitmapImage(outUri)' and you can see the relevant information here: picture. It seems from this picture that the image has loaded successfully.
I'm sure I'm missing something obvious, but I'm damned if I can say what it is. From my limited experience with WPF and XAML, it seems to be working okay, except for the lack of an image.
Well, if you can offer any help that would be much appreciated.
Edit: a couple of clarifications. The DataContext for the Treeview is called ScenesViewModel. This holds a list of SceneViewModel, and that list is called 'Scenes'. Each SceneViewModel containst a list of CharacterViewModel, and that is called 'Characters'. These two images show the relevant part of those classes, so hopefully the hierarchy can be understood. For each CharacterViewModel, there should be an ImgIcon.
Make sure that you bind to the view model (ScenesViewModel) property if the ImgIcon property is defined in this class:
<Image Source="{Binding DataContext.ImgIcon, RelativeSource={RelativeSource AncestorType=TreeView}}" Margin="2"/>
The DataContext of the StackPanel in the ItemTemplate is an item in the ItemsSource and not the view model.
Edit: You should define one template for SceneViewModel objects and another one for CharacterViewModel objects. You could put the implicit templates in the section of the TreeView:
<TreeView Grid.Column="0" Width="Auto" Background="Aquamarine" ItemsSource="{Binding Scenes}"
SelectedItemChanged="TreeView_SelectedItemChanged">
<TreeView.DataContext>
<viewModels:ScenesViewModel />
</TreeView.DataContext>
<TreeView.Resources>
<HierarchicalDataTemplate DataType="{x:Type local:SceneViewModel}" ItemsSource="{Binding Characters}">
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding SceneName}"></TextBlock>
<Image Source="{StaticResource ImgWorld}" Margin="0,0,5,0" Width="32" Height="32"/>
</StackPanel>
</HierarchicalDataTemplate>
<DataTemplate DataType="{x:Type local:CharacterViewModel}">
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding FirstName}"></TextBlock>
<Border BorderThickness="1" Background="RoyalBlue">
<Image Source="{Binding ImgIcon}" Margin="2"/>
</Border>
</StackPanel>
</DataTemplate>
</TreeView.Resources>
</TreeView>
This answer is for those looking to see what happened - maybe you have the same issue.
My problem was actually elsewhere in my code. When I loaded a Scene (data model) from my database to populate the SceneViewModel's data, I created one instance which I used to load the image, and then added a new instance to the list. So there were two instances, one which was created, used to load the images and then not used, and another which was used (but which didn't have the images loaded). Basically it was a simple mistake and not related to the code seen here - that all worked fine (since now that I corrected the error, things work as they should, images and all).
Thanks to all those who took the time to answer.

Multiple text items in List view item

Does anyone know of the correct way to add multiple sections of text to one listview item when using Windows 10 universal apps in C#? Items.subitems doesn't seem to work. Say my two string I want to display in a single list item are stored in...
public class listContent
{
public string heading { get; set; }
public string subHeading { get; set; }
}
Would doing something similar to this work?
listContent listItem = new listContent();
listItem.heading = "HEADING";
listItem.subHeading = "subheading";
lsvTransactions.Items.Add(listItem);
Regards
Nathan
Best way is to DataBind to a custom DataTemplate. Considering you already have lsvTransactions name ListView. Here is what you should do.
Create a List of listContent in your .cs file showed below
public class MainPage : Page
{
public List<listContent> ContentList {get; set;}
.....
Add Items to this ContentList in your code like you did above.
After adding items add the following line to point to data source.
lsvTransactions.DataContext = ContentList;
Create a ListView like shown below with DataTemplate
<ListView x:Name="lsvTransactions" ItemsSource={Binding}>
<ListView.ItemTemplate>
<DataTemplate>
<StackPanel>
<TextBlock Text="{Binding heading}"/>
<TextBlock Text="{Binding subheading}"/>
</StackPanel>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
As it was said maybe if you give us more info on your code, we can help better. I post you here a sample of a Listbox (which is similar to the listview) with the definition of the ItemTemplate for the elements I use and how to bind the items to the listbox and the fields in the items to the elements put in the Listbox.
<ListBox Grid.Row="0" Grid.Column="0" ItemsSource="{Binding Path=MeasureUnits, Mode=OneWay}"
SelectedItem="{Binding Path=SelectedMeasureUnit, Mode=TwoWay}"
SelectionMode="Single" Margin="0"
IsSynchronizedWithCurrentItem="True" >
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel>
<WrapPanel>
<TextBlock Text="{Binding Path=ID}" Style="{StaticResource idStyle}" />
<TextBlock Text="{Binding Path=Description}" Style="{StaticResource desStyle}"/>
</WrapPanel>
<WrapPanel>
<TextBlock Text="{Binding Path=DestinationUnitsInSource}" Style="{StaticResource numStyle}" />
<TextBlock Text="{Binding Path=SourceUnitsInDestination}" Style="{StaticResource numStyle}" />
</WrapPanel>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
In my XAML:
<ListView x:Name="listView">
In my C#:
listView.Items.Add(item);
Where Item is an object with multiple text elements.
If you post code I can be more specific.

WPF TreeView binding multiple sub-collections

I am building an application to manage connections, databases, tables, etc. I am needing to bind a collection of items with multiple sub-collections (see below). I am pretty new to WPF and am not sure if an answer to this question already exists. I've searched but haven't found any examples of the scenario I am faced with.
server1
-database1
--functions <- "static" node
---function1
---function2
--users <- "static" node
---user1
---user2
-database2
--functions <- "static" node
---function3
---function4
--users <- "static" node
---user3
---user4
When I try to bind it, I can get the data to display but it isn't in the format needed above. It's displayed like this.
server1
-database1
--function1
--function2
--user1
--user2
object hierarchy:
class DatabaseViewModel
{
public string Name
{
// normal getters and setters for 2way binding
}
public IObservableCollection<DbFunctionViewModel> Functions
{
// normal getters and setters for 2way binding
}
public IObservableCollection<DbUserViewModel> Users
{
// normal getters and setters for 2way binding
}
}
Markup:
<TreeView x:Name="Connections">
<TreeView.Resources>
<HierarchicalDataTemplate DataType="{x:Type viewModels:DbConnectionViewModel}" ItemsSource="{Binding Databases}">
<StackPanel Orientation="Horizontal">
<TextBlock x:Name="Name" Text="{Binding Name}" />
</StackPanel>
</HierarchicalDataTemplate>
<HierarchicalDataTemplate DataType="{x:Type viewModels:DbDatabaseViewModel}" ItemsSource="{Binding Children}">
<StackPanel Orientation="Horizontal">
<TextBlock x:Name="Name" Text="{Binding Name}" />
</StackPanel>
</HierarchicalDataTemplate>
<HierarchicalDataTemplate DataType="{x:Type viewModels:DbFunctionViewModel}">
<StackPanel Orientation="Horizontal">
<TextBlock x:Name="Name" Text="{Binding Name}" />
</StackPanel>
</HierarchicalDataTemplate>
<HierarchicalDataTemplate DataType="{x:Type viewModels:DbUserViewModel}">
<StackPanel Orientation="Horizontal">
<TextBlock x:Name="Name" Text="{Binding UserName}" />
</StackPanel>
</HierarchicalDataTemplate>
</TreeView.Resources>
</TreeView>
I tried returning a CompositeCollection containing both and it didn't seem to work the way i need it to.
public IList Children
{
get
{
return new CompositeCollection
{
new CollectionContainer { Collection = Functions },
new CollectionContainer { Collection = Users }
};
}
}
My question is, how do you bind all the users to a node named users and all the functions to a node named functions? Any advice would be greatly appreciated. Thanks!
To get your Children property to work, you need to define a data template for CollectionContainer:
<HierarchicalDataTemplate DataType="{x:Type viewModels:CollectionContainer}"
ItemsSource="{Binding Collection}">
<TextBlock Text="{Binding Name}"/>
</HierarchicalDataTemplate>
You should add a string Name property to CollectionContainer:
public IList Children
{
get
{
return new[]
{
new CollectionContainer("Functions", Functions),
new CollectionContainer("Users", Users),
};
}
}
A few hints:
Stack panels are redundant.
x:Name is redundant if you're binding using Text={Binding ...}.

How to bind a list of list to a ListView control

I have The following structure
QPage class Object contains a List<List<QLine>>
QLine object contains List<Qword>
every list of words constructs a line and every list of lines consists a group(paragraph) and every list of paragraphs consists a page.
I want to bind the page to structure like this in XAML
<ListView>
<StackPanel Orientation="Vertical">
<StackPanel Orientation="Horizontal">
<TextBlock>
</TextBlock>
</StackPanel>
</StackPanel>
</ListView>
where each item of the ListView is a paragraph(List<QLine>) and each vertical stack panel holds an item of the List<QLine> and each item of the horizontal stack panel holds an item of the List<Qword> and the texblock is bound to Qword.text property. I have no idea how to do such binding from the XAML code.
Hopefully I did not miss some list but this should work. Basically it's a ListBox that hosts List<List<QLine>> (called it QPageList). Then you have ItemsControl that hosts each List<QLine> in vertical panel and finally there is another ItemsControl that hosts List<Qword> from QLine (called it QwordList) where each QWord is displayed as TextBlock on horizontal StackPanel
<!-- ItemsSource: List<List<QLine>>, Item: List<QLine> -->
<ListBox ItemsSource="{Binding QPageList}">
<ListBox.ItemTemplate>
<DataTemplate>
<!-- ItemsSource: List<QLine>, Item: QLine -->
<ItemsControl ItemsSource="{Binding}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<!-- ItemsSource: QLine.List<QWord>, Item: QWord -->
<ItemsControl ItemsSource="{Binding QwordList}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Horizontal"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<!-- Item: QWord -->
<TextBlock Text="{Binding text}"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
What you are looking for is ListView.ItemTemplate. Basically, you need to provide your list with a way to understand the nested data structure of your rows.
Here is a good tutorial to get you started on ItemTemplates.
Once your list has an item template then you just bind the ListView directly to your data source and that's it.
Hopefuly this will prove helpful. Taken from here
Authors (list)
- - Name
- - Books (list)
| - - Title
| - - Contents
Sample code :
<Window.Resources>
<DataTemplate x:Key="ItemTemplate1">
<StackPanel>
<TextBlock Text="{Binding Name}"/>
</StackPanel>
</DataTemplate>
<DataTemplate x:Key="ItemTemplate2">
<StackPanel>
<TextBlock Text="{Binding Title}"/>
</StackPanel>
</DataTemplate>
</Window.Resources>
<Grid x:Name="LayoutRoot" DataContext="{Binding Source={StaticResource AuthorDataSource}}">
<ListBox x:Name="AuthorList" ItemTemplate="{DynamicResource ItemTemplate1}" ItemsSource="{Binding Authors }" >
<ListBox x:Name="BookList" DataContext="{Binding SelectedItem, ElementName=AuthorList}" ItemsSource="{Binding Books }" ItemTemplate="{DynamicResource ItemTemplate2}" />
<TextBlock x:Name="BookContent" DataContext="{Binding SelectedItem, ElementName=BookList}" Text="{Binding Contents }" />
</Grid>
Forward to what Alex is saying, it'd be a good idea to Encapsulate (If that's the correct word here) the object within classes, i.e.:
public class Page
{
public List<Paragraph> Paragraphs { get; set; }
}
public class Paragraph
{
public List<QWord> Sentences { get; set; }
}
public class Sentence
{
public List<QWord> Words { get; set; }
}
That'd help when you bind data in your XAML, i.e. if you look at the tutorial which Alex provided:
<TextBlock Text="Name: " />
<TextBlock Text="{Binding Name}" FontWeight="Bold" />
<TextBlock Text="{Binding Age}" FontWeight="Bold" />
<TextBlock Text=" (" />
<TextBlock Text="{Binding Mail}" TextDecorations="Underline" Foreground="Blue" Cursor="Hand" />
<TextBlock Text=")" />
Hope that helps you.

Categories