master detail binding in WPF - wpf

Why don't I see the details in this example. I cannot change the structure of dataclass, master class and detail class. So I have to solve this with the correct binding.
public class ViewModel
{
public dataclass data { get; set; }
public ViewModel()
{
data = new dataclass();
master a_master = new master();
a_master.mastername = "hello";
detail a_detail = new detail();
a_detail.detailname = "goodbye";
data.details.Add(a_detail);
data.Add(a_master);
}
}
public class dataclass : ObservableCollection<master>
{
public ObservableCollection<detail> details { get; set; }
public dataclass()
{
details = new ObservableCollection<detail>();
}
}
public class master
{
public string mastername { get; set; }
}
public class detail
{
public string detailname { get; set; }
}
And in my XAML I am binding like this:
<Window x:Class="md.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:vm="clr-namespace:md.viewmodels"
Title="MainWindow" Height="350" Width="525">
<Window.DataContext>
<vm:ViewModel/>
</Window.DataContext>
<StackPanel Orientation="Vertical" >
<ListView ItemsSource="{Binding Path=data}">
<ListView.View>
<GridView>
<GridViewColumn Header="master" DisplayMemberBinding="{Binding mastername}"/>
</GridView>
</ListView.View>
</ListView>
<ListView ItemsSource="{Binding Path=data/details}">
<ListView.View>
<GridView>
<GridViewColumn Header="detail" DisplayMemberBinding="{Binding detailname}"/>
</GridView>
</ListView.View>
</ListView>
</StackPanel>
</Window>

Try
<ItemsSource="{Binding Path=data.details}">
instead of
<ItemsSource="{Binding Path=data/details}">
I think what you tried to achieve was kind of a master/detail scenario with binding to hierarchical data like decribed in How to: Use the Master-Detail Pattern with Hierarchical Data. In fact, as long as you have an ObservableCollection<details> as property of a class derived from ObservableCollection<master> this is not hierarchical, and hence the / in the binding expression won't work. See PropertyPath XAML Syntax, section Source Traversal (Binding to Hierarchies of Collections) for details about the /.
Also there are widely accepted conventions for capitalization in C#, saying that you should use Pascal casing for public types like the classes and properties here.

Related

WPF DataGrid not loading data

I am a beginner in C# and WPF .
I have created a user control LogTable.atxml which contains a DataGrid and added it to the MainWindow.xaml .
The Table is displayed but the contents are not being fetched.
I think the issue is im not able to sent the Itemsource in the right way.
[Result]Please help.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace TableTest.UserControls
{
class Tabledata
{
string A{ get; set; }
string B { get; set; }
string C { get; set; }
public Tabledata(string a, string b, string c)
{
A = a;
B = b;
C =c;
}
}
}
namespace TableTest.UserControls
{
/// <summary>
/// Interaction logic for LogTable.xaml
/// </summary>
public partial class LogTable : UserControl
{
ObservableCollection<Tabledata> list;
public LogTable()
{
InitializeComponent();
list = getTableDetails();
this.logGrid.ItemsSource = list;
}
private ObservableCollection<Tabledata> getTableDetails()
{
ObservableCollection<Tabledata> list= new ObservableCollection<Tabledata>();
Tabledata data = new Tabledata("aaa", "aaa", "aaa");
Tabledata data1 = new Tabledata("bbb", "aaa", "aaa");
Tabledata data2 = new Tabledata("ccc", "aaa", "aaa");
list.Add(data);
list.Add(data1);
list.Add(data2);
return list;
}
}
}
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
}
}
<Window
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:UserControls="clr-namespace:TableTest.UserControls" x:Class="TableTest.MainWindow"
Title="MainWindow" Height="350" Width="525">
<Grid>
<UserControls:LogTable x:Name="logtable" HorizontalAlignment="Left" Margin="0,209,0,0" VerticalAlignment="Top" Width="287" Height="111"/>
</Grid>
</Window>
<UserControl x:Class="TableTest.UserControls.LogTable"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
mc:Ignorable="d"
>
<DataGrid x:Name="logGrid" AutoGenerateColumns="False"
Height="290"
HorizontalAlignment="Left"
VerticalAlignment="Top" Width="290"
ItemsSource="{Binding list}"
>
<DataGrid.Columns >
<DataGridTextColumn Binding="{Binding Path=A}" MinWidth="50" Header="Column 1"/>
<DataGridTextColumn Binding="{Binding Path=B}" MinWidth="50" Header="Column 2"/>
<DataGridTextColumn Binding="{Binding Path=C}" MinWidth="50" Header="Column 3"/>
</DataGrid.Columns>
</DataGrid>
</UserControl>
I think you need to do a few things
1. Your ItemSource needs to bind to a property. So your code should look something like
public partial class LogTable : UserControl
{
public ObservableCollection<Tabledata> list {get;set;}
public LogTable()
{
InitializeComponent();
DataContext=this;
list = new ObservableCollection<TableData>();
list = getTableDetails();
this.logGrid.ItemsSource = list;
}
You need to set your data context of your user control. If you are just using the codebehind you can get away with setting the DataContext in your usercontrols constructor like in the code above. But probably in the future you are going to want to use the mvvm pattern and set your datacontext to your viewmodel.
Note: You will need to set your datacontext of the mainwindow if you want to access any information from that window's codebehind (or whatever you want to bind data from).
Here is a good resource to read up on mvvm.
Update: Just saw your xaml. Since you named the Datagrid you can actually get away with not setting the DataContext as your as setting the ItemSource directly in your code. However, since you don't have your datacontext set you can remove the ItemSource={Binding list} from your xaml. That will only work if you have the list property available on your DataContext.
Update 2: You also need to make your properties public on your TableData class. then it will work
class Tabledata
{
public string A { get; set; }
public string B { get; set; }
public string C { get; set; }
public Tabledata(string a, string b, string c)
{
A = a;
B = b;
C = c;
}
}

WPF Specifying HierarchicalDataTemplate for Interface

I've found a really strange quirk in WPF. If I specify a DataTemplate for an interface, it will work if defined inside an ItemsControl.ItemTemplate, but will not work if defined inside ItemsControl.Resrouces.
Concrete example:
I have a tree structure I want to represent. All items in the tree implement IHardware, but they do not necessarily have a common base type. If I define a HierarchicalDataTemplate for IHardware inside TreeView.ItemTemplate, everything works swimmingly. If I define the template inside TreeView.Resources, it never gets used/applied. The following shows the same data in 2 columns, the first column works as expected, the second column does not.
<Window x:Class="WPFInterfaceBinding.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:self ="clr-namespace:WPFInterfaceBinding"
DataContext="{Binding RelativeSource={RelativeSource Self}}"
Title="MainWindow" Height="350" Width="525">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition />
<ColumnDefinition />
</Grid.ColumnDefinitions>
<!-- Works -->
<Border
Grid.Column="0"
Background="Gray">
<TreeView
ItemsSource="{Binding Hardware}">
<TreeView.ItemTemplate>
<HierarchicalDataTemplate
DataType="{x:Type self:IHardware}"
ItemsSource="{Binding SubHardware}">
<TextBlock Text="{Binding Path=Name}" />
</HierarchicalDataTemplate>
</TreeView.ItemTemplate>
</TreeView>
</Border>
<!-- Doesn't work -->
<Border
Grid.Column="1"
Background="Gray">
<TreeView
ItemsSource="{Binding Hardware}">
<TreeView.Resources>
<HierarchicalDataTemplate
DataType="{x:Type self:IHardware}"
ItemsSource="{Binding SubHardware}">
<TextBlock Text="{Binding Path=Name}" />
</HierarchicalDataTemplate>
</TreeView.Resources>
</TreeView>
</Border>
</Grid>
</Window>
Note that in the second column, nothing has changed except TreeView.ItemTemplate -> TreeView.Resources
Why is this the case? How can I get the template to work when inside Resources? I imagine I can work around this using a DataTemplateSelector, but first I'm curious if there's a way to actually get it working as expected.
Code behind, for completeness
using System.Windows;
namespace WPFInterfaceBinding
{
public partial class MainWindow : Window
{
public IHardware[] Hardware { get; private set; }
public MainWindow ()
{
Hardware = InitializeHardware();
InitializeComponent();
}
private IHardware[] InitializeHardware ()
{
return new Hardware[] {
new Hardware("Component 1", new Hardware[] {
new Hardware("Sub Component 1"),
new Hardware("Sub Component 2")
}),
new Hardware("Component 2", new Hardware[] {
new Hardware("Sub Component 3"),
new Hardware("Sub Component 4")
})
};
}
}
public class Hardware : IHardware
{
public string Name { get; set; }
public IHardware[] SubHardware { get; set; }
public Hardware ( string name, Hardware[] subHardware = null )
{
Name = name;
SubHardware = subHardware ?? new Hardware[0];
}
}
public interface IHardware
{
string Name { get; set; }
IHardware[] SubHardware { get; set; }
}
}
Additional information:
I can't simply use ItemTemplate because in my actual usage scenario there will be non-IHardware items mixed in using a CompositeCollection so I need multiple templates.
I can't change the types of the collections from IHardware to something concrete because I'm displaying data from code I don't control.
This is just example code, not representative of any design patterns actually in use.
Defining the template inside TreeView.Resources works just fine if the type is changed from IHardware to Hardware.
Turns out, WPF just doesn't like binding to interfaces. The only work around I could figure out was to use a DataTemplateSelector.
public class OHMTreeTemplateSelector : DataTemplateSelector
{
public HierarchicalDataTemplate HardwareTemplate { get; set; }
public DataTemplate SensorTemplate { get; set; }
public override DataTemplate SelectTemplate ( object item, DependencyObject container )
{
if ( item is IHardware ) return HardwareTemplate;
else if ( item is ISensor ) return SensorTemplate;
return base.SelectTemplate(item, container);
}
}
Though, for other reasons I ended up creating a separate ViewModel for the data that exposes it through concrete types, circumventing this issue.

ListView binding in WPF not showing expected collection items

Why isn't this binding working?
<Window x:Class="S3PackageInstaller.MainWindow" x:Name="Window1" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s3u="clr-namespace:S3PackageInstaller"
Icon="App.ico" Title="Sims 3 Package Installer" Height="480" Width="740">
<DockPanel LastChildFill="True">
<DockPanel DockPanel.Dock="Left" Width="200" VerticalAlignment="Stretch" LastChildFill="True"
Margin="20,20,0,20">
<!-- this is the binding that isn't working -->
<ListView Width="200" ItemsSource="{Binding ElementName=Window1, Path=InstalledPackages}">
<ListView.View>
<GridView>
<GridViewColumn Header="Installed Packages" DisplayMemberBinding="{Binding Filename}"></GridViewColumn>
</GridView>
</ListView.View>
</ListView>
<!-- snip -->
</Window>
Relevant code behind:
public partial class MainWindow : Window
{
public ObservableCollection<object> InstalledPackages { get; private set; }
public MainWindow()
{
InitializeComponent();
InstalledPackages = new ObservableCollection<object>();
LoadInstalledPackages();
}
private void LoadInstalledPackages()
{
var installPath = Settings.Default.TargetDirectory;
var packages = System.IO.Directory.GetFiles(installPath, "*.package");
InstalledPackages.Clear();
foreach (string filename in packages)
InstalledPackages.Add(new { Filename = filename });
}
// snip...
}
When I run the program, the ListView is empty. When debugging, I have verified that the collection contains items after LoadInstalledPackages is run.
I think the problem is that while your collection property is an ObservableCollection, so will notify of collection changes, the property itself does not raise a change notification when you first assign to it. When you create your window as follows:
public MainWindow()
{
InitializeComponent();
InstalledPackages = new ObservableCollection<object>();
LoadInstalledPackages();
}
When InitializeComponent is invoked your UI is created and bindings constructed. At this point InstalledPackages is null. In the next line you create your collection, but because InstalledPackages does not raise a PropertyChanged event, your binding is not updated.
Either implement INotifyPropertyChanged, or assign the ObservableCollection to this property before invoking InitializeComponent.

WPF: How do I have to setup my model and the binding strategy?

Regard this image:
alt text http://img25.imageshack.us/img25/9743/timetablepo.png
The TimeTableViewModel of this user interface is this:
public string SchoolclassCodeMonday {get;set;}
public string SchoolclassCodeTuesday {get;set;}
public string SchoolclassCodeWednesday {get;set;}
public string SchoolclassCodeThursday {get;set;}
public string SchoolclassCodeFriday {get;set;}
public string SchoolclassCodeSaturday {get;set;}
public string SchoolclassCodeSunday {get;set;}
The above would work when I would display only the properties as string in a textbox.
But what I want is to bind each combox to a mutual ObservableCollection
SchoolclassCodes and the SelectedItem aka DisplayMember of the ComboBox must somehow be
MAPPED to one of the 7 above Properties AND the SelectedItem if retrieved must supply a row
of all 7 schoolclass selected in the combobox.
Or what I actually want just in other words ;-)
Display in the ComboBox the SchoolclassCodes list, set the Value of SelectedItem.SchoolclassCode"WeekdayName" to the Value of the selected ComboboxItem.SchoolclassCode
Well there are some ideas I have but all lack some experience to make them fully working.
I could add to the TimeTableViewModel for each Property a ObservableCollection
SchoolclassCodes but that seems very redundant to me. Why should I hold 7 lists for ONE row when each cell has the same list with the same items in it ?
Any suggestions concerning the ViewModels structure and the binding in Wpf are welcome :)
UPDATE: My SchoolclassCodes list is dynamically created, so I there is no possibility about static binding or hardcode string items in XAML...
UPDATE2:
OK I tried to make it working with MVVM:
I had to change the ObservableCollection ClassCodes
to ObservableCollection SchoolclassCodes as the Schoolclass object
has a reference to Pupil class with strings thats not possible.
Schoolclass.cs:
public string SchoolclassCode {get;set;}
...
TimeTableWeekViewModel.cs:
public ObservableCollection<Schoolclass> SchoolclassCodes
{
get { return _schoolclassCodes; }
set
{
_schoolclassCodes = value;
this.RaisePropertyChanged("SchoolclassCodes");
}
}
XAML:
How must the binding look like NOW because the SchoolclassCodes is not found by wpf ?
I recommend that you look into the MVVM (Model-View-ViewModel) pattern if you are doing anything remotely complicated. Also, it is useful to implement INotifyPropertyChanged in your model/viewmodel (e.g. the MyWeek class in my example below). That will notify any other bindings whenever one of your SchoolclassCode<Day> properties is changed.
Here is some simple sample code to get you started:
using System.Collections.ObjectModel;
using System.Linq;
namespace BindingSample
{
public partial class Window1
{
public Window1()
{
InitializeComponent();
SchoolclassCodes = new ObservableCollection<string>(
Enumerable.Range(1, 10).Select(i => "Code #" + i));
MyWeeks = new ObservableCollection<MyWeek>(
Enumerable.Range(1, 5).Select(i => new MyWeek() {SchoolclassCodeMonday = SchoolclassCodes.First()}));
DataContext = this;
}
public ObservableCollection<string> SchoolclassCodes { get; private set; }
public ObservableCollection<MyWeek> MyWeeks { get; private set; }
}
public class MyWeek
{
public string SchoolclassCodeMonday { get; set; }
public string SchoolclassCodeTuesday { get; set; }
public string SchoolclassCodeWednesday { get; set; }
public string SchoolclassCodeThursday { get; set; }
public string SchoolclassCodeFriday { get; set; }
public string SchoolclassCodeSaturday { get; set; }
public string SchoolclassCodeSunday { get; set; }
}
}
<Window x:Class="BindingSample.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Window1" Height="300" Width="300">
<Window.Resources>
<CollectionViewSource x:Key="ClassCodes" Source="{Binding SchoolclassCodes}" />
</Window.Resources>
<ListView ItemsSource="{Binding MyWeeks}">
<ListView.View>
<GridView>
<GridView.Columns>
<GridViewColumn Header="Monday">
<GridViewColumn.CellTemplate>
<DataTemplate>
<ComboBox SelectedValue="{Binding SchoolclassCodeMonday}"
ItemsSource="{Binding Source={StaticResource ClassCodes}}"
IsSynchronizedWithCurrentItem="False" />
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
<!-- Other columns here... -->
</GridView.Columns>
</GridView>
</ListView.View>
</ListView>
</Window>
Since the columns must be auto generated, putting this code in the appropriate place (Loaded event?) should do what you want :
ObservableCollection<String> Collection = GetCollection();
foreach (DataGridComboBoxColumn column in DataGrid1.Columns.OfType<DataGridComboBoxColumn>())
{
column.ItemsSource = Collection;
}
Of course, some modifications must be applied!
You don't need to mess around with the DataGrid, and you certainly don't need to create any class with seven different properties that you have to implement property-change notification for. The Grid control makes calendars easy.
Your view model should have three classes: Month, Week, and Day. The Week class contains a list of Day objects, and the Month class has a list of Week objects. Each class should have a reference to its parent, i.e. Week in Day and Month in Week.
The Week class's constructor should initialize its Days property to be a list of 7 Day objects. The Month class's constructor has to have more specific logic in it for setting up its Weeks property; I'll leave that to you.
The Day object should expose these properties:
public DayOfWeek DayNumber { get; private set; }
public ObservableCollection<SchoolclassCode> Codes { get { return Week.Codes; } }
as well as a read/write string Code property that does property-change notification.
The Week object should expose:
public ObservableCollection<SchoolclassCode> Codes { get { return Month.Codes; } }
public IEnumerable<Day> Days { get; private set; }
public IEnumerable<string> Codes
{
get { return Days.Select(x => x.Code); }
}
You can then define a data template for presenting days:
<DataTemplate DataType="{x:Type local:Day}">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition SharedSizeGroup="Sunday"/>
<ColumnDefinition SharedSizeGroup="Monday"/>
<ColumnDefinition SharedSizeGroup="Tuesday"/>
<ColumnDefinition SharedSizeGroup="Wednesday"/>
<ColumnDefinition SharedSizeGroup="Thursday"/>
<ColumnDefinition SharedSizeGroup="Friday"/>
<ColumnDefinition SharedSizeGroup="Saturday"/>
</Grid.ColumnDefinitions>
<ComboBox
Grid.Column="{Binding DayNumber}"
ItemsSource="{Binding Codes}"
SelectedValue="{Binding Code, Mode=TwoWay}"/>
</Grid>
</DataTemplate>
and weeks:
<DataTemplate DataType="{x:Type Week}">
<ItemsControl ItemsSource="{Binding Days}">
<ItemsControl.ItemTemplate>
<ItemsPanelTemplate>
<StackPanel Orientation="Horizontal"/>
</ItemsPanelTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</DataTemplate>
and months:
<DataTemplate DataType="{x:Type Month}">
<ItemsControl ItemsSource="{Binding Weeks}" Grid.IsSharedSizeScope="True">
<ItemsControl.ItemTemplate>
<ItemsPanelTemplate>
<StackPanel Orientation="Vertical"/>
</ItemsPanelTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</DataTemplate>
Adding day names and week numbers to the above templates is straightforward.

WPF UserControl weird binding problem

Im usign a Ribbon Window and in the "content area beneath" I have a grid in which I will be displaying UserControls. To demonstrate my problem lets take a look at this simple UserControl:
<ListView x:Name="lvPersonList">
<ListView.View>
<GridView>
<GridViewColumn Width="120" Header="Name" DisplayMemberBinding="{Binding Name}"/>
<GridViewColumn Width="120" Header="Height" DisplayMemberBinding="{Binding Height}"/>
</GridView>
</ListView.View>
</ListView>
And the code:
public partial class MyUserControl: UserControl
{
private List<Person> personList;
public TestSnpList()
{
InitializeComponent();
this.personList = new List<Person>();
this.personList.Add(new Person { Name = "Chuck Norris", Height = 210 });
this.personList.Add(new Person { Name = "John Rambo", Height = 200 });
this.lvPersonList.ItemsSource = personList;
}
}
public class Person
{
public string Name { get; set; }
public int Height { get; set; }
}
The parent Window:
<Grid x:Name="grdContent" DockPanel.Dock="Top">
<controls:MyUserControl x:Name="myUserControl" Visibility="Visible"/>
</Grid>
I don't understant why this binding doesn't work. Instead of values (Name and Height) I get full class names. If I use this code in a Window it works fine.
Any ideas? I would like this user contorl works for itself (it gets the data form the DB and represents it in a ListView)...
Thanks!
It seems the problem is with RibbonWindow.
If I use Window and UserControl binding works fine, but if I use RibbonWindow (Odyssey Ribbon) binding doesn't work. What I don't understand is that in design mode I can see proper values and in running mode I see only class names:
http://i977.photobucket.com/albums/ae255/HekoSLO/designModeVSrunning.png

Resources