Hello I am trying to databind ListBoxes residing inside ItemTemplate of LLS, so far I have seen grouping applied to a template containing only Textblocks, but I need the 'collection' being grouped by. I know my requirements are not that much practical but I am badly in need of it.
MY MODEL
public class ItemViewModel
{
private string _id;
public string ID
{
get
{
return _id;
}
set
{
if (value != _id)
{
_id = value;
NotifyPropertyChanged("ID");
}
}
}
private string _lineOne;
public string LineOne
{
get
{
return _lineOne;
}
set
{
if (value != _lineOne)
{
_lineOne = value;
NotifyPropertyChanged("LineOne");
}
}
}
private string _lineTwo;
public string LineTwo
{
get
{
return _lineTwo;
}
set
{
if (value != _lineTwo)
{
_lineTwo = value;
NotifyPropertyChanged("LineTwo");
}
}
}
}
MY VIEWMODELS
public class GroupedItemViewModel
{
public string Key2
{
get
{
return _key2;
}
set
{
if (value != _key2)
{
_key2 = value;
NotifyPropertyChanged("Key2");
}
}
}
private ObservableCollection<ItemViewModel> _grp;
public ObservableCollection<ItemViewModel> GroupedItems
{
get
{
return _grp;
}
set
{
if (value != _grp)
{
_grp= value;
NotifyPropertyChanged("GroupedItems");
}
}
}
}
public class MainViewModel : ViewModelBase,INotifyPropertyChanged
{
public MainViewModel()
{
this.Items = new ObservableCollection<ItemViewModel>();
}
public ObservableCollection<ItemViewModel> Items { get; private set; }
public ObservableCollection<GroupedItemViewModel> GroupedPhotos
{
get
{
var finalQuery = Items
.GroupBy(category => category.LineOne)
.Select(grouping => new GroupedItemViewModel { Key2 = grouping.Key, GroupedItems = grouping.ToObservableCollection<ItemViewModel>() });
return new ObservableCollection<GroupedItemViewModel>(finalQuery);
}
}
}
MY VIEW - LLS
<Grid x:Name="ContentPanel">
<phone:LongListSelector ItemsSource="{Binding GroupedPhotos}" ItemTemplate="{StaticResource DataTemplate3}" GroupHeaderTemplate="{StaticResource header}"/>
</Grid>
MY VIEW - DATA TEMPLATE OF LLS
<DataTemplate x:Key="DataTemplate3">
<Grid>
<ListBox ItemsSource="{Binding GroupedItems}" ItemTemplate="{StaticResource DataTemplate4}" ItemsPanel="{StaticResource ItemsPanelTemplate2}"/>
</Grid>
</DataTemplate>
MY VIEW - DATA TEMPLATE OF LISTBOX
<DataTemplate x:Key="DataTemplate4">
<Grid>
<TextBlock Text="{Binding LineOne}"/>
</Grid>
</DataTemplate>
MY VIEW - DATA TEMPLATE OF GROUP HEADER
<DataTemplate x:Key="header">
<Grid>
<TextBlock Text="{Binding Key2}"/>
</Grid>
</DataTemplate>
Related
when typing in the textbox and click "Add Employee", i want it to update and display to the datagrid, i've implemented INotifyPropertyChanged and RelayCommand. what am i missing that's not populating the data. thanks in advance
here is my model
public class EmployeeModel
{
public string Name { get; set; }
public int Pedicure { get; set; }
public int Tip { get; set; }
public int Total { get; set; }
}
this is my ViewModel
List<EmployeeModel> employeeModel = new List<EmployeeModel>() { };
private ICommand _addEmployeeCommand;
public ICommand AddEmployeeCommand
{
get
{
return _addEmployeeCommand ?? (_addEmployeeCommand = new RelayCommand(x => { AddNewEmployee(); }));
}
}
public List<EmployeeModel> Employee
{
get { return employeeModel; }
set
{
if(value != employeeModel)
{
employeeModel = value;
OnPropertyChanged("Employee");
}
}
}
private string employeeName;
public string EmployeeName
{
get { return employeeName; }
set
{
if (value != employeeName)
{
employeeName = value;
OnPropertyChanged("EmployeeName");
}
}
}
public void AddNewEmployee()
{
Employee.Add(new EmployeeModel { Name = EmployeeName });
}
here is my View
<TabItem Header="Employee">
<StackPanel Orientation="Vertical">
<DataGrid ItemsSource="{Binding Employee}">
</DataGrid>
<StackPanel Orientation="Horizontal">
<TextBlock Text="Name: "/>
<TextBox Text="{Binding EmployeeName}"
Width="40"
Height="15"
VerticalAlignment="Top"/>
<Button Content="Add"
Command="{Binding AddEmployeeCommand}"
Height="20"
VerticalAlignment="Top"/>
</StackPanel>
</StackPanel>
(I pluralized the name Employee to Employees in this answer for future readers)
The problem is with the Source of the DataGrid
Bear in mind that OnPropertyChanged("Employees") only notifies about the changes made to the Employees and is not responsible for any changes made within Employees.
To be clear, it only works when you do employeeModels = new List<EmployeeModel>()
And won't be called when Employees.Add(employee)
Hopefully WPF has its own ObservableCollection type that will take care of that:
private ObservableCollection<Employee> _employees = new ObservableCollection<Employee>;
public ObservableCollection<Employee> Employees { get { return _employees; } }
I have a ComboBox in my XAML. It is populated with the following code:
PopulateColors.cs
public class PopulateColors
{
public ObservableCollection<ItemsColors> itemsColors { get; set; }
public PopulateColors()
{
this.itemsColors = new ObservableCollection<ItemsColors>();
this.itemsColors.Add(new ItemsColors{ ItemColumn = "Blue", IdItemColumn = 0 });
this.itemsColors.Add(new ItemsColors{ ItemColumn = "Red", IdItemColumn = 1 });
this.itemsColors.Add(new ItemsColors{ ItemColumn = "Pink", IdItemColumn = 2 });
}
}
public class ItemsColors
{
public string ItemColumn { get; set; }
public int IdItemColumn { get; set; }
}
pagedemo.xaml.cs:
ClothingViewModel ClothVM = null;
public pagedemo()
{
this.comboColors.DataContext = new PopulateColors();
}
protected override void OnNavigatedTo(NavigationEventArgs e)
{
ClothVM = new ClothingViewModel();
ClothVM = ClothVM.GetData(1);
this.DataContext = ClothVM ;
navigationHelper.OnNavigatedTo(e);
}
private void SaveButton_Click(object sender, RoutedEventArgs e)
{
string result = ClothVM .Save( ClothVM );
if (result.Contains("OK"))
{
//to do
}
}
pagedemo.xaml (XAML design)
<TextBox x:Name="txtJersey"
Text="{Binding Jersey, Mode=TwoWay}"/>
<ComboBox Name="comboColors"
ItemsSource="{Binding itemsColors}"
DisplayMemberPath="ItemColumn"
SelectedValuePath="ItemColumn"/>
Ok. items are display fine in ComboBox.
QUESTION:
I need to save the selected color in a table of the database, using MVVM pattern. But how? I have this code. But I do not know how to link it with the ComboBox:
Model: Clothing.cs
Public class Clothing
{
public string Color { get; set; }
public string Jersey { get; set; }
}
ViewModel: ClothingViewModel.cs
public class ClothingViewModel : ViewModelBase
{
public string Save (ClothingViewModel cloth)
{
string result = string.Empty;
using (var db = new SQLite.SQLiteConnection(App.DBPath))
{
string change = string.Empty;
try
{
var existing = (db.Table<Clothing>().Where(
c => c.id == 1)).SingleOrDefault();
if (existing!= null)
{
existing.Color = cloth.Color;
existing.Jersey = cloth.Jersey;
int success = db.Update(existing);
}
}
catch
{ }
}
}
private int id = 1;
public int ID
{
get
{ return id; }
set
{
if (id == value)
{ return; }
id= value;
RaisePropertyChanged("ID");
}
}
private string color = string.Empty;
public string Color
{
get
{ return color; }
set
{
if (color == value)
{ return; }
color = value;
isDirty = true;
RaisePropertyChanged("Color");
}
}
private string jersey = string.Empty;
public string Jersey
{
get
{ return jersey; }
set
{
if (jersey == value)
{ return; }
jersey = value;
isDirty = true;
RaisePropertyChanged("Jersey");
}
}
}
Actually, there are plenty of options. Let's demonstrate just a few of them.
1. Use Binding with RelativeSource to find Ancestor with appropriate DataContext
XAML-code:
<!-- Please use Page instead of Window. -->
<Window>
<StackPanel>
<TextBox x:Name="txtJersey"
Text="{Binding Jersey, Mode=TwoWay}"/>
<!-- Use {x:Type Page} instead of {x:Type Window}. -->
<ComboBox Name="comboColors" ItemsSource="{Binding itemsColors}"
DisplayMemberPath="ItemColumn"
SelectedValue="{Binding
RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Window}},
Path=DataContext.Color}"
SelectedValuePath="ItemColumn" />
</StackPanel>
</Window>
2. Use Binding with ElementName to the UI-element with appropriate DataContext
XAML-code:
<StackPanel>
<TextBox x:Name="txtJersey"
Text="{Binding Jersey, Mode=TwoWay}"/>
<ComboBox Name="comboColors" ItemsSource="{Binding itemsColors}"
DisplayMemberPath="ItemColumn"
SelectedValue="{Binding
ElementName=txtJersey,
Path=DataContext.Color}"
SelectedValuePath="ItemColumn" />
</StackPanel>
3. Use "one" ViewModel that contains another ViewModel
public class ClothingViewModel : ViewModelBase
{
private readonly PopulateColors colors = new PopulateColors();
public PopulateColors Colors
{
get { return this.colors; }
}
...
}
Page:
// This is a Page (Window in case of using WPF).
public class ClothingWindow
{
public ClothingWindow()
{
InitializeComponent();
// Note: no need to set the DataContext for the ComboBox.
DataContext = new ClothingViewModel();
}
}
XAML-code:
<StackPanel>
<TextBox Text="{Binding Jersey, Mode=TwoWay}"/>
<ComboBox ItemsSource="{Binding Colors.itemsColors}"
DisplayMemberPath="ItemColumn"
SelectedValue="{Binding Color}"
SelectedValuePath="ItemColumn" />
</StackPanel>
References
Data Binding (WPF), MSDN.
Data Binding in WPF, John Papa, MSDN Magazine.
I am using TreeView to display my data. I want to bind foreground of a tree view item. Here is my code below.
View:
<UserControl.Resources>
<HierarchicalDataTemplate x:Key="TreeViewItem" ItemsSource="{Binding Children}">
<StackPanel Orientation="Horizontal">
<CheckBox Margin="2" IsChecked="{Binding IsChecked, Mode=TwoWay}" Content="{Binding Title}"
Background="{Binding Path=ForegroundColor}" IsThreeState="True"/>
</StackPanel>
</HierarchicalDataTemplate>
</UserControl.Resources>
<Grid>
<TreeView Margin="5, 0, 5, 0" ItemsSource="{Binding GeoGraphixModules}" ItemTemplate="{StaticResource TreeViewItem}" IsEnabled="{Binding TreeViewEnabled}" />
</Grid>
And in my view model
public class SomeTreeViewItem
{
public Collection Children
{
get { return _children; }
}
public Brush ForegroundColor
{
get
{
if (SomeCheck)
return Brushes.Green;
else
return Brushes.Red;
}
}
}
Now when I debug this application 'ForegroundColor' is hit but the application still displays black as foreground for all the child items. What is the problem here?
After trying to create the same error i made the following viewmodel
public class ViewModel
{
private Collection<GeoGraphixModule> _geoGraphixModules;
public ViewModel()
{
_geoGraphixModules = new Collection<GeoGraphixModule>();
_geoGraphixModules.Add(new GeoGraphixModule(){Children = new Collection<Bla>(){new Bla{Children = new Collection<Bla>{new Bla(), new Bla(), new Bla()}}}});
_geoGraphixModules.Add(new GeoGraphixModule(){Children = new Collection<Bla>(){new Bla{Children = new Collection<Bla>{new Bla(), new Bla(), new Bla()}}}});
_geoGraphixModules.Add(new GeoGraphixModule(){Children = new Collection<Bla>(){new Bla{Children = new Collection<Bla>{new Bla(), new Bla(), new Bla()}}}});
_geoGraphixModules.Add(new GeoGraphixModule(){Children = new Collection<Bla>(){new Bla{Children = new Collection<Bla>{new Bla(), new Bla(), new Bla()}}}});
}
public Collection<GeoGraphixModule> GeoGraphixModules
{
get { return _geoGraphixModules; }
set { _geoGraphixModules = value; }
}
}
public class GeoGraphixModule
{
public Brush ForegroundColor
{
get
{
if (SomeCheck())
return Brushes.Green;
return Brushes.Red;
}
}
private bool SomeCheck()
{
Random random = new Random();
int randomNumber = random.Next(0, 100);
if ((randomNumber % 2) == 0)
return true;
return false;
}
private Collection<Bla> _children;
public Collection<Bla> Children { get; set; }
}
public class Bla
{
private bool? _isChecked;
private string _title;
private Collection<Bla> _children;
public bool? IsChecked
{
get { return _isChecked; }
set
{
_isChecked = value;
}
}
public Collection<Bla> Children
{
get { return _children; }
set { _children = value; }
}
public string Title
{
get { return _title; }
set
{
_title = value;
}
}
}
Its very ugly i know but it works for the top level take a loot at the below image
I have a PrimaryItems List & foreach PrimaryItem there is a SecondaryItems list.So i used a ListBox as ItempTemplate of another ListBox.
<ListBox ItemsSource="{Binding PrimaryItems}">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel>
<TextBlock Text="{Binding Name}"/>
<ListBox ItemsSource="{Binding SecondaryItems}" SelectedItem="{Binding SelectedSecondaryItem}" ScrollViewer.VerticalScrollBarVisibility="Disabled" ScrollViewer.HorizontalScrollBarVisibility="Disabled">
<ListBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Name}"/>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
My View Model Code
private List<PrimaryItem> _primaryItems;
public List<PrimaryItem> PrimaryItems
{
get { return _primaryItems; }
set { _primaryItems = value;RaisePropertyChanged(); }
}
//SecondaryItems list is inside in each PrimaryItem
//private List<SecondaryItem> _secondaryItems;
//public List<SecondaryItem> SecondaryItems
//{
// get { return _secondaryItems; }
// set { _secondaryItems = value; RaisePropertyChanged(); }
//}
private SecondaryItem _selectedSecondaryItem;
public SecondaryItem SelectedSecondaryItem
{
get { return _selectedSecondaryItem; }
set
{
_selectedSecondaryItem = value;
if (_selectedSecondaryItem != null)
{
//TO DO
}
}
}<br/>
This is the class structure
public class PrimaryItem
{
public int Id { get; set; }
public string Name { get; set; }
public List<SecondaryItem> SecondaryItems{ get; set; }
}
public class SecondaryItem
{
public int Id { get; set; }
public string Name { get; set; }
}
and I set SelectedItem Binding to the Second ListBox.
But am not getting the Selection Trigger on Second ListBox.
Can we use a ListBox inside another ListBox` Template ? If yes how do we overcome this problem?
First of all, use ObservableCollection instead of List since it implements INotifyPropertyChanged interface.
As far as I understand your requirements, PrimaryItem class should has a property SecondaryItems. So remove it from ViewModel and paste to PrimaryItem class (as well as SelectedSecondaryItem property):
private ObservableCollection<SecondaryItem> _secondaryItems;
public ObservableCollection<SecondaryItem> SecondaryItems
{
get { return _secondaryItems; }
set { _secondaryItems = value; RaisePropertyChanged(); }
}
EDIT:
I've fully reproduced your situation and get it working.
Classes:
public class PrimaryItem
{
public int Id { get; set; }
public string Name { get; set; }
public List<SecondaryItem> SecondaryItems { get; set; }
private SecondaryItem _selectedSecondaryItem;
public SecondaryItem SelectedSecondaryItem
{
get { return _selectedSecondaryItem; }
set
{
_selectedSecondaryItem = value;
if (_selectedSecondaryItem != null)
{ // My breakpoint here
//TO DO
}
}
}
}
public class SecondaryItem
{
public int Id { get; set; }
public string Name { get; set; }
}
ViewModel:
public class MyViewModel : ViewModelBase
{
private List<PrimaryItem> _primaryItems;
public List<PrimaryItem> PrimaryItems
{
get { return _primaryItems; }
set { _primaryItems = value; RaisePropertyChanged("PrimaryItems"); }
}
public ErrorMessageViewModel()
{
this.PrimaryItems = new List<PrimaryItem>
{
new PrimaryItem
{
SecondaryItems =
new List<SecondaryItem>
{
new SecondaryItem { Id = 1, Name = "First" },
new SecondaryItem { Id = 2, Name = "Second" },
new SecondaryItem { Id = 3, Name = "Third" }
},
Name = "FirstPrimary",
Id = 1
}
};
}
}
View:
<Window x:Class="TestApp.Views.MyView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:vm="clr-namespace:TestApp.ViewModels;assembly=TestApp.ViewModels"
xmlns:my="http://schemas.microsoft.com/wpf/2008/toolkit"
Title="Title" Height="240" Width="270" ResizeMode="NoResize"
WindowStartupLocation="CenterOwner" WindowStyle="ToolWindow">
<Window.DataContext>
<vm:MyViewModel/>
</Window.DataContext>
<Grid>
<ListBox ItemsSource="{Binding PrimaryItems}">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel>
<TextBlock Text="{Binding Name}"/>
<ListBox ItemsSource="{Binding SecondaryItems}" SelectedItem="{Binding SelectedSecondaryItem}" ScrollViewer.VerticalScrollBarVisibility="Disabled" ScrollViewer.HorizontalScrollBarVisibility="Disabled">
<ListBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Name}"/>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</Grid>
</Window>
You can try to use LinqToVisualTree, it can get alomost all Controls in your app, you just have to select what you want to find(in your case ListBoxItem), and then cast it to your model. I used it, when I needed to get text from TextBox which was in ListboxItem. But it also fits to your task.
I have a listbox that is bound to an observablecollection. The observable collection contains a list of objects, each with it's own observablecollection. What i want is to click an item in the first listbox and have it's list of things displayed in the second listbox. Can I do this in pure WPF?
Just bind the ItemsSource of the second listbox to the SelectedItem of the first list box.
Edit: here is some code.
public partial class MainWindow : Window
{
public MainWindow()
{
TestItems = new ObservableCollection<Test>();
InitializeComponent();
for (int i = 0; i < 5; i++)
TestItems.Add(InitTest(i));
}
public ObservableCollection<Test> TestItems { get; set; }
private Test InitTest(int index)
{
Test test = new Test();
test.Name = "Test" + index.ToString();
test.Test2Items = new ObservableCollection<Test2>();
for (int i = 0; i <= index; i++)
{
Test2 test2 = new Test2();
test2.Label = test.Name + "_label" + i.ToString();
test.Test2Items.Add(test2);
}
return test;
}
}
public class Test
{
public string Name { get; set; }
public ObservableCollection<Test2> Test2Items { get; set; }
public override string ToString()
{
return Name;
}
}
public class Test2
{
public string Label { get; set; }
public override string ToString()
{
return Label;
}
}
Xaml
<Window x:Class="WpfApplication1.MainWindow"
x:Name="MyWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="WPF Example" Height="300" Width="400">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<ListBox x:Name="ListBox1" Grid.Column="0" ItemsSource="{Binding TestItems, ElementName=MyWindow}" />
<ListBox Grid.Column="1" ItemsSource="{Binding SelectedItem.Test2Items, ElementName=ListBox1}" />
</Grid>
</Window>
Your view models could look something like this: (I am using my BindableBase here)
class MainViewModel : Bindablebase {
public ObservableCollection<ItemViewModel> Items { get; private set; }
private ItemViewModel _selectedItem;
public ItemViewModel SelectedItem {
get { return _selectedItem; }
set { SetProperty(ref _selectedItem, value, "SelectedItem"); }
}
}
class ItemViewModel : BindableBase {
public ItemViewModel (string name) {
Name = name;
Items = new ObservableCollection<string>();
}
public string Name { get; private set; }
public ObservableCollection<string> Values { get; private set; }
private string _selectedValue;
public string SelectedValue {
get { return _selectedValue; }
set { SetProperty(ref _selectedValue, value, "SelectedValue"); }
}
}
And then your view would have:
<ComboBox ItemsSource="{Binding Items}"
SelectedItem="{Binding SelectedItem}"
DisplayMemberPath="Name"/>
<!--
Note that the DataContext here could be ommitted
and the bindings would be like {Binding SelectedItem.Values}
-->
<ComboBox DataContext="{Binding SelectedItem}"
ItemsSource="{Binding Values}"
SelectedItem="{Binding SelectedValue}"/>