Master-detail data binding - wpf

I have two models in my application:
class Line
{
string line_id { get; set; }
string color { get; set; }
}
class Point
{
string point_id { get; set; }
string line_id { get; set; }
int weight { get; set; }
}
And I have two ObservableCollection:
lines - ObservableCollection<Line>
points - ObservableCollection<Point>
I want to display two ListBox'es : first (outer) to display lines, and second (inner) to display the points which belongs to this line.
<ListView x:Name="lvPoint" ItemsSource="{Binding}">
<ListView.ItemTemplate>
<DataTemplate>
<StackPanel>
<TextBlock Text="{Binding color, Mode=TwoWay}" />
<ListBox ItemsSource="{Binding SOMETHING, Mode=TwoWay}">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel>
<TextBlock Text="{Binding weight, Mode=OneWay}" />
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</StackPanel>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
I set DataContext for outer ListBox in the code:
lvPoint.DataContext = lines;
How can I set DataContext for inner ListBox to display Points for each Line ?

Your Line model is not good for this scenario. It should have a property such as called Points which contains the interested points belonging to the Line. Then the Binding is just simple:
class Line {
public string line_id { get; set; }
public string color { get; set; }
ObservableCollection<Point> _points;
public ObservableCollection<Point> Points {
get {
if (_points == null) _points = new ObservableCollection<Point>();
return _points;
}
}
}
Then in XAML code you can just set the Path to Points like this:
<ListBox ItemsSource="{Binding Points, Mode=TwoWay}">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel>
<TextBlock Text="{Binding weight, Mode=OneWay}" />
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
The model above is just an example showing the main idea. The full implementation should of course be different and more advanced depending on the current project.
Update: Without using the model above, you can try using Converter for the Binding. The Binding is set directly to the current item (Line). The Converter will convert Line to points (maybe based on line_id and your query method):
public class LineToPointsConverter : IValueConverter {
public object Convert(object value, Type targetType, object parameter,
System.Globalization.CultureInfo culture){
var line = value as Line;
//convert to points by querying the points based on line.line_id here
return ...
}
public object ConvertBack(object value, Type targetType,
object parameter,
System.Globalization.CultureInfo culture){
return Binding.DoNothing;
}
}
Define a static property of LineToPointsConverter or create an instance of that converter in Resources:
<Window.Resources>
<local:LineToPointsConverter x:Key="lineToPointsConverter"/>
</Window.Resources>
Then in XAML code set that converter:
<ListBox ItemsSource="{Binding Mode=TwoWay, Path=.,
Converter={StaticResource lineToPointsConverter}}">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel>
<TextBlock Text="{Binding weight, Mode=OneWay}" />
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>

Related

How to Bind a Dictionary with the Key value being a Binding Property in WPF?

I have a class like this:
public class Person
{
public int PersonId { get; set; }
public string Name { get; set; }
public int AccountId { get; set; }
public Dictionary<int, List<string>> Values { get; set; }
}
I have a DataGrid in my XAML where I want to display the first index of the List<string> within the dictionary property Values as one of the column values, where the key being passed in will be the AccountId. The ItemSource of my DataGrid is a list of Person objects coming from my ViewModel, and the DataGrid has 3 columns, PersonId, Name, and Value (where value is the first index of the List<string> collection in the dictionary item)
I have seen examples on stackoverflow and other places on the internet that try to do this but none of the solutions worked for me.
Here is my XAML code:
<DataGrid Name="MyDataGrid" ItemsSource="{Binding Persons}">
<DataGrid.Columns>
<DataGridTemplateColumn Header="ID">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBox Text="{Binding PersonId}" IsEnabled="False" />
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
<DataGridTemplateColumn Header="Name">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBox Text="{Binding Name}" IsEnabled="False" />
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
<DataGridTemplateColumn Header="Value">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBox Text="{Binding Values[{Binding AccountId}][0]}" IsEnabled="False" />
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
</DataGrid>
The last column is the column where I try to use a {Binding} as the key value, but it does not work. If I hard-code a valid AccountId, it works. Has anyone encountered this before?
Thanks!
one of view model purposes is to provide data to view in a convenient format. code to get value from dictionary by key and then return first item can be written in a view model, not in converter:
public class Person
{
public int PersonId { get; set; }
public string Name { get; set; }
public int AccountId { get; set; }
public Dictionary<int, List<string>> Values { get; set; }
public string AccountIdValue { get { return Values[AccountId].FirstOrDefault(); } }
}
then bind to that helper property:
<TextBox Text="{Binding AccountIdValue}" IsEnabled="False" />

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

WPF DataGridTemplateColumn, bind to generic CellTemplate

I'm very new to WPF so sorry if this is obvious but I can't seem to find any decent examples on the internet showing how it's done.
I have a DataGrid which is bound to a collection of DataItem called MyCollection. I want to create a generic DataTemplate that I can use for multiple columns in the grid (and elsewhere in the application should I need it).
E.g.
<DataGrid ItemsSource="{Binding MyCollection}" AutoGenerateColumns="False" SelectionUnit="Cell" EnableColumnVirtualization="True">
<DataGrid.Columns>
<DataGridTemplateColumn Header="File path" CellTemplate="{StaticResource FileSelectorEditorTemplate}" CellEditingTemplate="{StaticResource FileSelectorEditorTemplate}" />
<DataGridTemplateColumn Header="File path2" CellTemplate="{StaticResource FileSelectorEditorTemplate}" CellEditingTemplate="{StaticResource FileSelectorEditorTemplate}" />
<DataGridTemplateColumn Header="File path3" CellTemplate="{StaticResource FileSelectorEditorTemplate}" CellEditingTemplate="{StaticResource FileSelectorEditorTemplate}" />
...
My DataTemplate is defined at the moment in my Application resources as
<DataTemplate x:Key="FileSelectorEditorTemplate">
<Grid>
<TextBox Text="{Binding FilePath.PhysicalPath}" HorizontalAlignment="Stretch" Margin="0,0,35,0" />
<Button Content="..." Height="25" Width="25" Margin="0,0,5,0" HorizontalAlignment="Right" Click="FileOpen_Click" />
</Grid>
</DataTemplate>
Now the problem is that the binding is specified in the DataTemplate, whereas I need to apply a different binding for each of the properties FilePath, FilePath2, FilePath3 on the view model. I don't seem to be able to specify the Binding on the DataGridTemplateColumn though?
I'd appreciate any pointers in the right direction,
Thanks!
The binding on the DataGridTemplateColumn is specified in its CellTemplate.
If you want different bindings for the three columns, I'd say you would have to make a different DataTemplate for each of the columns. There might be some workaround, but I doubt it would be pretty.
Edit:
Having different templates you can use a DataTemplateSelector to select the right template for the current object.
Using IValueConverter (Just a quick sketch, but should work):
<DataTemplate x:Key="GenericTemplate" >
<TextBlock FontSize="14" >
<TextBlock.Text>
<Binding Converter="{StaticResource NewValue}" Path="Me" />
</TextBlock.Text>
</TextBlock>
</DataTemplate>
public class NewValueConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
someContainer obj = value as someContainer;
if (obj.type == MyType.First)
return (string)(obj.val1);
else
return (string)(obj.val2);
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
public enum MyType
{
First,
Second
}
public class someContainer
{
public someContainer Me { get; set; }
public string val1 { get; set; }
public string val2 { get; set; }
public MyType type;
public someContainer()
{
Me = this;
val1 = "string1";
val2 = "string2";
}
}
...
public ObservableCollection<someContainer> myList {get; set;}
...
<StackPanel Margin="0,10,0,0" Orientation="Vertical" Grid.Column="2">
<ItemsControl ItemsSource="{Binding MyList}" ItemTemplate="{StaticResource GenericTemplate}" />
</StackPanel>
If you can't use the option of Jesper Gaarsdal you might also be able to use a CellStyle and define the binding in the column-declaration.
see this SO for example: How to reuse WPF DataGridTemplateColumn (including binding)

Binding properties to CustomDataTemplate

I have a list of items
public List<Item> MyItems { get; set; }
displayed on the DataGrid. One column shows status "icon" which is defined by template.
Code looks something like that:
Column template [...]
<DataGridTemplateColumn Header="Status">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<Grid Height="18" Width="35">
<Rectangle Fill="{Binding Status.Background}" />
<TextBlock Text="{Binding Status.Text}" />
</Grid>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
Data model [...]
public class Item
{
public int ItemId { get; set; }
public string Name { get; set; }
public int StatusId { get; set; }
public Status Status { get; set; }
}
public class Status
{
public int StatusId { get; set; }
public int Text { get; set; }
public Brush Background
{
get
{
//Colour logic goes here
}
}
}
I'd like to remove colour logic from the data model and put it to the resource file instead.
<DataGridTemplateColumn Header="Status" CellTemplate="{StaticResource MyCustomTemplate}" </DataGridTemplateColumn>
I hope so far I am going to right direction but at this point I am lost as I don't know how to bind Status property (or StatusId) to MyCustomTemplate.
If anyone could help my with this it would be great.
EDIT
This works fine.
<DataGridTemplateColumn Header="V" Width="25" IsReadOnly="True" >
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<ic:CloseIcon Visibility="{Binding DockStatus, Converter={StaticResource CloseIconDisplayVisibilityConverter}}" />
<ic:DockIcon Visibility="{Binding DockStatus, Converter={StaticResource DockIconDisplayVisibilityConverter}}" />
<ic:UndockIcon Visibility="{Binding DockStatus, Converter={StaticResource UndockIconDisplayVisibilityConverter}}" />
</StackPanel>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
This doesn't refresh UI when model changes (DockStatus changes)
<DataGridTemplateColumn Header="V" Width="25" IsReadOnly="True" CellEditingTemplateSelector="{StaticResource DockIconCellTemplateSelector}}">
If it's only about selecting the proper background colour for a specific item, or items with a specific StatusId, you could write a binding converter. The converter would simply convert an int to a Brush:
[ValueConversion(typeof(int), typeof(Brush))]
public class StatusColorConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if (value is int)
{
int statusId = (int)value;
// create Brush from id here and return it
}
return null;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotSupportedException();
}
}
You would modify your Binding like this:
<Rectangle Fill="{Binding Status.StatusId, Converter={StaticResource StatusColorConverter}}" />

Treeview binding problem

I have a window MainWindow.xaml and
private static Tutorial tutorial; there.
Also I have class Structure.cs where I describe child types
public class Tutorial
{
public string Name { get; set; }
public IList<Chapter> Chapters = new List<Chapter>();
}
public class Chapter
{
public string Name { get; set; }
public IList<Unit> Units = new List<Unit>();
}
public class Unit
{
public string Name { get; set; }
public IList<Frame> Frames = new List<Frame>();
...
}
I want to bind tutorial structure to treeview. How can I do this?
I tried this way.
<TreeView Grid.Row="2" x:Name="treeViewStruct" Margin="5,0,5,0" Background="LemonChiffon" BorderBrush="Bisque" BorderThickness="1" ScrollViewer.VerticalScrollBarVisibility="Auto" IsTextSearchEnabled="True" Cursor="Hand">
<TreeView.Resources>
<HierarchicalDataTemplate DataType = "{x:Type Structure:Chapter}"
ItemsSource = "{Binding Path=Units}">
<TextBlock Text="{Binding Path=Name}"/>
</HierarchicalDataTemplate>
<DataTemplate DataType="{x:Type Structure:Unit}">
<TextBlock Text="{Binding Path=Name}"/>
</DataTemplate>
</TreeView.Resources>
</TreeView>
It doesn't work.
Please, help! I'm a newbie in WPF. I need dynamic tree
so that when I add a chapter or a unit in the object tutorial, tree is updated.
And for this way of binding please throw the idea how can I get a collection item, when I selected some tree node.
This may help :
<HierarchicalDateTemplate DataType = "{x:Type local:Tutorial}"
ItemsSource="{Binding Chapters}">
<TextBlock Text="{Binding Name}"/>
</HierarchicalDateTemplate>
<HierarchicalDateTemplate DataType = "{x:Type local:Chapter}"
ItemsSource="{Binding Units}"
<TextBlock Text="{Binding Name}"/>
</HierarchicalDateTemplate>
<DateTemplate DataType = "{x:Type local:Unit}"
<TextBlock Text="{Binding Name}"/>
</DateTemplate>

Resources