databinding of object property in dictionary - c#

I have question about databinding in XAML. I have dictionary in which are my objects. Now in the view I want show in first listbox key of dictionary (with this no problem), but in nested listbox show values of that object.
So - my code in XAML:
<ListBox ItemsSource="{Binding Dict}" Margin="0,0,171,0">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel>
<TextBlock Text="{Binding Path=Key}" />
<ListBox ItemsSource="{Binding Path=Value}">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel>
<TextBlock Text="{Binding Path=Key}" />
<TextBlock Text="{Binding Path=Value}" /> <!-- HERE -->
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
following with code from ViewModel:
public MainWindow()
{
InitializeComponent();
t = new TestModel();
t._dict = new Dictionary<string, Dictionary<string, myDrive>>();
t._dict.Add("folder1", new Dictionary<string, myDrive>());
t._dict["folder1"].Add("file1", new myDrive() { size = "71" });
t._dict.Add("folder2", new Dictionary<string, test>());
t._dict["folder2"].Add("file1", new myDrive() { size = "54" });
t._dict["folder2"].Add("file2", new myDrive() { size = "30" });
this.DataContext = t;
}
and code from model:
public Dictionary<string, Dictionary<string, myDrive>> _dict;
public Dictionary<string, Dictionary<string, myDrive>> Dict
{
get
{
return this._dict;
}
}
and class myDrive is simple:
public class myDrive
{
public string size = "";
public string getSize()
{
return this.size;
}
}
My goal is show parameter size in that textbox so I tried different ways like:
<TextBlock Text="{Binding Path=Value.size}" />
<TextBlock Text="{Binding Path=Value[size]}" />
<TextBlock Text="{Binding Path=Value.getSize}" />
but no luck :(. Only if I put Value like in example, than can see output string : "AppName.myDrive".
Thanks for any help

First define property for size variable. You can only Databind properties not variables.
public class myDrive
{
public string size = "";
public string Size{get{return size;}}
public string getSize()
{
return this.size;
}
}
Once this is done, you can bind like below
<ListBox ItemsSource="{Binding Dict}" Margin="0,0,171,0">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel>
<TextBlock Text="{Binding Path=Key}" />
<ListBox ItemsSource="{Binding Path=Value}">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel>
<TextBlock Text="{Binding Path=Key}" />
<TextBlock Text="{Binding Path=Value.Size}" />
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>

Related

WPF Binding Listbox into ListView (different Source / datacontext)

i have these two classes.
classes:
public class OuterList
{
public string Process { get; set; }
public OuterList(string _process)
{
Process = _process;
}
}
public class InnerList
{
public string Order { get; set; }
public string Product { get; set; }
public InnerList(string _order, string _product)
{
Order = _order;
Product = _product;
}
}
And i will get a Listbox in a ListView.
XAML:
<ListView x:Name="Container" ItemsSource="{Binding OuterList}">
<ListView.ItemTemplate>
<DataTemplate>
<StackPanel>
<TextBlock Margin="5" FontSize="24" FontFamily="Arial" Background="Red" Text="{Binding Process}"/>
<ListBox x:Name="Orders" ItemsSource="{Binding RelativeSource={RelativeSource AncestorType=Grid}, Path=DataContext.InnerList}">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel>
<TextBlock Text="{Binding Product}"/>
<TextBlock Text="{Binding Order}"/>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</StackPanel>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
Code:
public partial class MainWindow : Window
{
ObservableCollection<OuterList> myContainers = new();
ObservableCollection<InnerList> myItems = new();
public MainWindow()
{
InitializeComponent();
myContainers.Add(new OuterList("Start"));
myContainers.Add(new OuterList("Middle"));
myContainers.Add(new OuterList("End"));
Container.ItemsSource = myContainers;
myItems.Add(new InnerList("34545","SD5"));
myItems.Add(new InnerList("45654", "SD5"));
myItems.Add(new InnerList("65775", "SD5"));
myItems.Add(new InnerList("78677", "SD5"));
myItems.Add(new InnerList("35887", "SD5"));
Orders.ItemsSource = myItems; //<- The name "Orders" does not exist in the current context.
}
}
All the Content of the OuterList will shown, but no Item from InnerList
How can i add Content from two different ItemSources? Or, what am I doing wrong?
Got an issue in Code: The name "Orders" does not exist in the current context.
Thanks for you Help!
Because your DataContext is incorrect.
Assuming the Grid that wraps both of them its DataContext contains Outer and Inner
You can do this:
<Grid>
<ListView x:Name="Container" ItemsSource="{Binding OuterList}">
<ListView.ItemTemplate>
<DataTemplate>
<StackPanel>
<TextBlock Margin="5" FontSize="24" FontFamily="Arial" Background="Red" Text="{Binding Process}"/>
<ListBox x:Name="Orders" ItemsSource="{Binding RelativeSource={RelativeSource AncestorType=Grid}, Path=DataContext.InnerList}">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel>
<TextBlock Text="{Binding Product}"/>
<TextBlock Text="{Binding Order}"/>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</StackPanel>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</Grid>

Changing TextBox's Text in a ListView does not update ObservableCollection<string> Source

I have a quite complicated structure of ListViews. Inside this structure are TextBoxes with values from my ViewModel. When I change a value in some textbox, the property in ViewModel doesn't update. The "AllTexts" property in ViewModel still contains only "Hello" strings.
Basically, I want to show user structure of strings and then let the user change this structure. After he finishes his modification I want to save his changes. "Hello" strings are here just for testing.
My ViewModel:
class MainWindowViewModel
{
public ObservableCollection<ObservableCollection<ObservableCollection<string>>> AllTexts { get; set; }
public int SelectedGroupIndex { get; set; }
public int SelectedColumnIndex { get; set; }
public ICommand AddGroup { get; private set; }
public ICommand AddColumn { get; private set; }
public MainWindowViewModel()
{
this.AllTexts = new ObservableCollection<ObservableCollection<ObservableCollection<string>>>();
this.SelectedGroupIndex = -1;
this.SelectedColumnIndex = -1;
this.AddGroup = new Command(this.AddGroupCommandHandler);
this.AddColumn = new Command(this.AddColumnCommandHandler);
}
private void AddGroupCommandHandler()
{
var tempColumn = new ObservableCollection<string>() { "Hello", "Hello", "Hello", "Hello", "Hello" };
var tempGroup = new ObservableCollection<ObservableCollection<string>>();
tempGroup.Add(tempColumn);
this.AllTexts.Add(new ObservableCollection<ObservableCollection<string>>(tempGroup));
}
private void AddColumnCommandHandler()
{
if (this.SelectedGroupIndex >= 0 && this.SelectedGroupIndex < this.AllTexts.Count)
{
var tempColumn = new ObservableCollection<string>() { "Hello", "Hello", "Hello", "Hello", "Hello" };
this.AllTexts[this.SelectedGroupIndex].Add(tempColumn);
}
}
}
My View:
<Window.Resources>
<ResourceDictionary>
<local:MainWindowViewModel x:Key="vm" />
</ResourceDictionary>
</Window.Resources>
<Grid Margin="10,10,10,10" VerticalAlignment="Top">
<Grid.RowDefinitions>
<RowDefinition Height="300" />
<RowDefinition />
<RowDefinition />
</Grid.RowDefinitions>
<ListView Grid.Row="0"
ItemsSource="{Binding AllTexts, Source={StaticResource vm}, Mode=TwoWay}"
Background="Red"
SelectedIndex="{Binding SelectedGroupIndex, Source={StaticResource vm}}">
<ListView.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Horizontal" />
</ItemsPanelTemplate>
</ListView.ItemsPanel>
<ListView.ItemTemplate>
<DataTemplate>
<ListView
Background="Yellow"
ItemsSource="{Binding Path=., Mode=TwoWay}"
SelectedIndex="{Binding SelectedColumnIndex, Source={StaticResource vm}}">
<ListView.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Horizontal" />
</ItemsPanelTemplate>
</ListView.ItemsPanel>
<ListView.ItemTemplate>
<DataTemplate>
<ListView
Background="Green"
ItemsSource="{Binding Path=., Mode=TwoWay}">
<ListView.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Vertical" />
</ItemsPanelTemplate>
</ListView.ItemsPanel>
<ListView.ItemTemplate>
<DataTemplate>
<TextBox Text="{Binding Path=., Mode=TwoWay, NotifyOnSourceUpdated=True}"
VerticalContentAlignment="Center"
HorizontalContentAlignment="Center"
Width="100" Height="40"/>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
<StackPanel Grid.Row="1" Orientation="Horizontal" Margin="0,20,0,0">
<Button Content="Add Group" Width="120" Height="30"
Command="{Binding AddGroup, Source={StaticResource vm}}" />
<Button Content="Add Column" Margin="20,0,0,0" Width="120" Height="30"
Command="{Binding AddColumn, Source={StaticResource vm}}" />
</StackPanel>
<StackPanel Grid.Row="2" Orientation="Horizontal" Margin="0,20,0,0">
<TextBlock Width="120" Height="30" FontSize="20"
Text="{Binding SelectedGroupIndex, Source={StaticResource vm}}" />
<TextBlock Width="120" Height="30" Margin="20,0,0,0" FontSize="20"
Text="{Binding SelectedColumnIndex, Source={StaticResource vm}}" />
</StackPanel>
</Grid>
Could someone, please help me?
Thank you.
Your ViewModel has to notify the View about the changes, or else the View retains original values of the ViewModel
In this case, string cannot notify the changes made to itself. Only its enclosing observable collection can notify about changes made to itself like add or remove and does not monitor further into its elements.
So you need an observable string:
public class MyString : DependencyObject
{
public string Value
{
get { return (string)GetValue(ValueProperty); }
set { SetValue(ValueProperty, value); }
}
public static readonly DependencyProperty ValueProperty =
DependencyProperty.Register("Value", typeof(string), typeof(MyString), new PropertyMetadata(""));
}
To use in the collection:
public ObservableCollection<ObservableCollection<ObservableCollection<MyString>>> AllTexts { get; set; }
I also added the following line to the MyString class in order to test the code and it worked.
public static MyString Hello { get { return new MyString { Value = "Hello" }; } }
Obviously, this is how it will be used:
var tempColumn = new ObservableCollection<MyString>() { MyString.Hello, MyString.Hello, MyString.Hello, MyString.Hello, MyString.Hello };
In xaml there are also some unnecessary things which you can get rid of:
use ItemsSource="{Binding}" for both ListViews, and use Text="{Binding Value}" for the TextBox. (there is no need for explicit TwoWay in any of those)

Add items to custom ListBox

I've this template of ListBoxItem that contains an Image and TextBlock. How to add an Item to this ListBox from code?
<ListBox Name="listBox">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal" >
<Image Source="{Binding}" Width="16" />
<TextBlock Text="{Binding}" Margin="5,0,0,0" />
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
I am assuming you will want to have a class which has imagesource and text properties in the lines of
public class TestClass()
{
public string ImageSrc {get; set;}
public string DisplayText {get; set;}
}
Add the objects to your collection
listBox.Items.Add(new TestClass() { ImageSrc = "blahblah", DisplayTest = "Test Display Text" });
and so on
Then you can use xaml in the lines of
<ListBox Name="listBox">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal" >
<Image Source="{Binding ImageSrc}" Width="16" />
<TextBlock Text="{Binding DisplayText}" Margin="5,0,0,0" />
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>

Bind a textblock in a nested list member

I have these classes:
public class Datum
{
public string name{get;set;}
public string id{get;set}
public List<Brewery> breweries { get; set; }
.
.
}
public class Brewery
{
public string name { get; set; }
.
.
}
And this Listbox
<ListBox ItemsSource="{Binding}" HorizontalAlignment="Left" Height="580" Margin="0,80,0,0" VerticalAlignment="Top" Width="446">
<ListBox.ItemTemplate>
<DataTemplate>
<ListBoxItem Tap="ListBoxItem_Tap" Tag="{Binding Path=id}">
<TextBlock Name="First" Text="{Binding Path=name}" />
<TextBlock Name="Second" Foreground="Gray" Text="{Binding Path=breweries.name}" />
</ListBoxItem>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
So I create a list of Datum objects and I bind the listbox to it. In that way, the First textblock is bound to the property 'name' of the Datum class.That works perfectly. What I want is the Second textblock to be bound to the property 'name', of the first item of the Brewery list.
If breweries was not a List I would easily do that, but since I take the info from a json, I can't change that.
From your example, if I understand what you're doing correctly, you should just be able to use an index to bind to the desired item from the collection. I'm not sure it's the best approach, but it should yield the result you're after.
E.g. (adapting your example):
<ListBox ItemsSource="{Binding}" HorizontalAlignment="Left" Height="580" Margin="0,80,0,0" VerticalAlignment="Top" Width="446">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel>
<ListBoxItem Tap="ListBoxItem_Tap" Tag="{Binding Path=id}">
<TextBlock Name="First" Text="{Binding Path=name}" />
<TextBlock Name="Second" Foreground="Gray" Text="{Binding Path=breweries[0].name}" />
</StackPanel>
</ListBoxItem>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>

Nested Listbox wont display nested list

<ListBox Height="434" HorizontalAlignment="Left" Margin="6,238,0,0" Name="listBox1" VerticalAlignment="Top" Width="432" DataContext="{Binding}">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel>
<TextBlock Text="{Binding Name}" Margin="4" Foreground="{StaticResource PhoneAccentBrush}"></TextBlock>
<StackPanel Orientation="Horizontal" Margin="4">
<TextBlock Text="Set" Margin="16" Foreground="{StaticResource PhoneAccentBrush}" />
<TextBlock Text="Weight" Margin="16" Foreground="{StaticResource PhoneAccentBrush}" />
<TextBlock Text="Reps" Margin="10,16,0,16" Foreground="{StaticResource PhoneAccentBrush}" />
</StackPanel>
<ListBox Name="setsAndReps" Height="auto" Width="auto" ItemsSource="{Binding Sets}">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBox Text="{Binding SetNumber}"/>
<TextBox Text="{Binding Weight}"/>
<TextBox Text="{Binding Reps}"/>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
The outer listbox's item source is being set to an observable collection of a user defined class called excercise
public class excercise : IComparable, IEquatable<excercise>, INotifyPropertyChanged
{
string name;
int max;
int NUM_SETS;
ObservableCollection<set> sets;
public event PropertyChangedEventHandler PropertyChanged;
public string Name
{
get { return this.name; }
set { this.name = value; }
}
public excercise(string name)
{
this.name = name;
this.NUM_SETS = 0;
this.sets = new ObservableCollection<set>();
}
public ObservableCollection<set> Sets
{
get{return this.sets; }
}
public ObservableCollection<set> getSets()
{
return this.sets;
}
}
The properties in the inner list box are from the set class but none of them are being displayed and I am not sure what the problem is.
your first listbox doesn't have any itemsource set and the datacontext is set to an empty binding (both on line 1 of your code)

Categories