A known "issue" with nesting ListBoxes in a Windows Phone 7 App is that for each parent category their respective child ListBox retains its own SelectedItems list. Well, I have a situation where this is expected behavior, but I'm having issues capturing both the Parent and Child ListBox selected lists.
Current Functionality:
1. List item
2. List item
3. Loading of Parent and Child ListBox data is working
4. Multi-select of Parent ListBox items works prefectly
5. Multi-select of Child ListBox items works, but is not accessible
6. Multi-select of Child ListBox items for a number of different parents works in the UI, but the selection is lost when scrolling large list sets and is not accessible
7. lstCategory is accessible directly, but lstSubCategory is not accessible directly (possibly, I just don't know how)
8. I'm bound to a ViewModel with one complex object that represents the two ListBoxes as two List objects.
Expected Functionality:
I would like to be able to select both Category (parent) and SubCategory (child) ListBox items as follows; (X) Denotes Selected:
Bread (X)
Loaf (X)
Croissant
Buscuit (X)
Donut
Fruit
Pinaple (X)
Strawberry
Drinks (X)
Water
Milk (X)
Juice (X)
Soda
Snacks (X)
Chips
Fries
Trail Mix
I would like to retain the selections even if this was a long list. So, what I want to capture and work with is:
Bread (X)
Loaf (X)
Buscuit (X)
Pinaple (X)
Drinks (X)
Milk (X)
Juice (X)
Snacks (X)
Since I have a CategoryID in each of the items' objects, I can strip the heirarchy information on capture.
For breivity, here is the essence of the code:
<ListBox
x:Name="lstCategory"
SelectionMode="Multiple"
ItemsSource="{Binding Categories}"
FontSize="32"
Margin="0,0,0,67">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel>
<StackPanel Orientation="Vertical">
<TextBlock Text="{Binding CategoryName}"
FontSize="36"
TextWrapping="Wrap"
Margin="20,0,0,0"
VerticalAlignment="Top"/>
<StackPanel Orientation="Vertical" Margin="60,0,0,0">
<ListBox
x:Name="lstSubCategory"
SelectionMode="Multiple"
ItemsSource="{Binding SubCategories}">
<ListBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding SubCategoryName}"
FontSize="28"
TextWrapping="Wrap"
VerticalAlignment="Top"/>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</StackPanel>
</StackPanel>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
And the ViewModel:
public List<Category> Categories { get; set; }
public PostCategorySelectVM()
{
Categories = new List<Category>()
{
new Category()
{
CategoryID = 0,
CategoryName = "Bread",
SubCategories = new List<SubCategory>()
{
new SubCategory() {
CategoryID = 001,
SubCategoryName = "Loaf"
},
new SubCategory() {
CategoryID = 002,
SubCategoryName = "Croissant"
}
// ...
}
// ...
}
// ...
}
}
Category Class:
public class Category
{
public int CategoryID { get; set; }
public string CategoryName { get; set; }
public List<SubCategory> SubCategories { get; set; }
}
SubCategory Class:
public class SubCategory
{
public int CategoryID { get; set; }
public string SubCategoryName { get; set; }
}
Save Button Click Event:
private void btnSave_Click(object sender, RoutedEventArgs e)
{
foreach (Category item in lstCategory.SelectedItems)
{
catList.Add(item);
}
foreach (Category cat in catList)
{
scatList = cat.SubCategories;
foreach (SubCategory scat in scatList)
{
// How do I select the "Selected" SubCategories?
// How do I select the lstSubCategory control?
}
}
}
Final Notes:
The only lead I have has do do with dependancy properties, but the only examples I've seen require the FrameworkPresentation.dll which is not available on WP7.
The nested ListBox has the expected UI functionality (except for large lists removing cross-selections on scroll)
The user experience feels best when both Category and SubCategory are shown on the same screen.
Consider the UI functionality like a directory search engine. You may want to select the general category and/or the sub categories in different combinations, but the parent should not require a child and a child should not require a parent, yet both child and parent could exist (for specificity).
You could use checkboxes instead of textboxes in your data templates, and then bind the IsChecked property of the checkboxes to an IsSelected property in your Category/Subcategory classes:
<ListBox x:Name="lstCategory"
ItemsSource="{Binding Categories}"
FontSize="32"
Margin="0,0,0,67">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Vertical">
<CheckBox Content="{Binding CategoryName}"
FontSize="36"
IsChecked="{Binding IsSelected,Mode=TwoWay}"
Margin="20,0,0,0"
VerticalAlignment="Top"/>
<ListBox ItemsSource="{Binding SubCategories}" Margin="60,0,0,0">
<ListBox.ItemTemplate>
<DataTemplate>
<CheckBox Content="{Binding SubCategoryName}"
FontSize="28"
IsChecked="{Binding IsSelected,Mode=TwoWay}"
VerticalAlignment="Top"/>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
You should also have your category/subcategory classes implement INotifyPropertyChanged to properly fire notifications when IsSelected is set.
assuming that, your save would look something like (this isnt' exact!)
private void btnSave_Click(object sender, RoutedEventArgs e)
{
catList.Clear();
catList.AddRange( lstCategory.Items.OfType<Category>().Where(x=>x.IsSelected));
scatList.Clear();
foreach (Category cat in catList)
{
scatList.AddRange(cat.SubCategories.Where(x=>x.IsSelected));
}
}
Related
I have a Pivot element that has a header template and an itemtemplate with a listbox which also has a itemtemplate. The header template is bound to a list of "DOWS" which i create via code. This works fine. Each DOW has a entityset TDs and i want to bind the listbox itemtemplate to this source. Once i bound it, deleting an item updates the ui fine but adding an item doesn't update the ui.
the entityset is based off of the "Local database with mvvm" example here:
http://msdn.microsoft.com/en-us/library/windowsphone/develop/hh286405(v=vs.105).aspx
I've researched for a few days and cant find anything helpful. some solutions i found where to wrap the entityset in a observable collection but that didnt seem to work. Another one was to use BindingLists, but entitySets dont have "ToBindingList()" for windows phone 7. Ive created observable collections in my ViewModel that i could bind to, but then i have no way of telling the listbox itemtemplate which collection to bind to when its created.
Any ideas?
I Can post more code if needed.
Thanks!!
Example Observablen Collections in ViewModel
private ObservableCollection<TDItem> _twoDaysAgoItems;
public ObservableCollection<TDItem> TwoDaysAgoItems
{
get { return _twoDaysAgoItems; }
set
{
_twoDaysAgoItems = value;
NotifyPropertyChanged("TwoDaysAgoItems");
}
}
private ObservableCollection<TDItem> _yesterdayItems;
public ObservableCollection<TDItem> YesterdayItems
{
get { return _yesterdayItems; }
set
{
_yesterdayItems = value;
NotifyPropertyChanged("YesterdayItems");
}
}
private ObservableCollection<TDItem> _todayItems;
public ObservableCollection<TDItem> TodayItems
{
get { return _todayItems; }
set
{
_todayItems = value;
NotifyPropertyChanged("TodayItems");
}
}
EntitySet
private EntitySet<TDItem> _tds;
[Association(Storage = "_ts", OtherKey = "_dowId", ThisKey = "Id")]
public EntitySet<TDItem> TDs
{
get { return this._tds;
}
set {
NotifyPropertyChanging("TDs");
this._tds.Assign(value);
NotifyPropertyChanged("TDs");
}
}
public DOW()
{
_tds = new EntitySet<TDItem>(
new Action<TDItem>(this.attach_TD),
new Action<TDItem>(this.detach_TD)
);
}
Pivot
<controls:Pivot x:Name="TDPivotDisplay" SelectedIndex="2" Margin="0,-10,0,0" Grid.Row="0" Title="TD" Foreground="White" FontWeight="light" VerticalAlignment="Top" FontSize="29.333" Style="{StaticResource PivotStyle1}" FontFamily="{StaticResource Akzidenz Grotesk}">
<controls:Pivot.HeaderTemplate>
<DataTemplate>
<StackPanel>
<TextBlock x:Name="DOW" Foreground="{Binding Colour}" FontSize="110" FontFamily="{StaticResource Akzidenz Grotesk}" Text="{Binding Header}"/>
<TextBlock x:Name = "FullDate" Foreground="{Binding Colour}" FontWeight="light" FontSize="30" FontFamily="{StaticResource Akzidenz Grotesk}" HorizontalAlignment="center" Text="{Binding Date}"/>
</StackPanel>
</DataTemplate>
</controls:Pivot.HeaderTemplate>
<controls:Pivot.ItemTemplate>
<DataTemplate>
<Grid Height="421">
<ListBox ItemsSource = "{Binding TDs}" ItemTemplate = "{StaticResource ListboxItemTemp}" x:Name ="TDLists" Margin="12, 0, 12, 0" Width="440"/>
</Grid>
</DataTemplate>
</controls:Pivot.ItemTemplate>
</controls:Pivot>
(Windows Phone 7 SDK)
Hi,
I have a ListBox named CTransactionList, and adding some items to this listbox by using data bindings. And Here I have a class to evaulate data bindings.(I think my listbox's XAML code is not needed here as my issue comes out due to some coding problems)
public class CTransaction
{
public String Date1 { get; set; }
public String Amount1 { get; set; }
public String Type1 { get; set; }
public CTransaction(String date1, String amount1, String type1)
{
this.Date1 = date1;
this.Amount1 = amount1;
switch (type1)
{
case "FR":
this.Type1 = "Images/a.png";
break;
case "TA":
this.Type1 = "Images/b.png";
break;
case "DA":
this.Type1 = "Images/c.png";
break;
}
}
}
Here I have a function, when a move completes, this function runs;(this function is supposed to add new items when function runs)
List<CTransaction> ctransactionList = new List<CTransaction>();//Define my list
public void movecompleted()
{
String DefaultDate = "";
String DefaultAmount = "";
String RandomType = "";
DefaultDate = nameend.Text;
DefaultAmount = diffend.Text;
RandomType = "FR";
ctransactionList.Add(new CTransaction(DefaultDate, DefaultAmount, RandomType));
CTransactionList.ItemsSource = ctransactionList;
}
For the first time when move completes, it adds the required elements to my list. But for next times, it does not add to my list. The old one keeps its existence. I tried also this format by getting list definition into my function like:
public void movecompleted()
{
List<CTransaction> ctransactionList = new List<CTransaction>(); //List definition in function
String DefaultDate = "";
//...Same
}
And this time, it replaces my current item with new one. Do not append at the end of list. (Both ways, I have one item in my list, not more) How can I do everytime append to list? Where am I wrong?
Here is my Debugging report. Both ctransactionList object and CTransactionList ListBox have the needed items according to my observations in debug watcher. Only problem, CTransactionList cant refresh itself properly even if it has the resources retrieved from ctransactionList object.
Here is my XAML code for my relevant listbox.(IF NEEDED)
<Grid>
<ListBox Name="CTransactionList" Margin="0,0,0,0" >
<ListBox.ItemTemplate >
<DataTemplate>
<Button Width="400" Height="120" >
<Button.Content >
<StackPanel Orientation="Horizontal" Height="80" Width="400">
<Image Source="{Binding Type1}" Width="80" Height="80"/>
<StackPanel Orientation="Vertical" Height="80">
<StackPanel Orientation="Horizontal" Height="40">
<TextBlock Width="100" FontSize="22" Text="Name:" Height="40"/>
<TextBlock Width="200" FontSize="22" Text="{Binding Date1}" Height="40"/>
</StackPanel>
<StackPanel Orientation="Horizontal" Height="40">
<TextBlock Width="100" FontSize="22" Text="Difficulty:" Height="40"/>
<TextBlock Width="200" FontSize="22" Text="{Binding Amount1}" Height="40"/>
</StackPanel>
</StackPanel>
</StackPanel>
</Button.Content>
</Button>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</Grid>
Thanks in advance.
As you rightly mentioned, it's not related to your XAML. Problem is in your code.
First simple fix may to clear the ItemsSource before setting the new source, like this
CTransactionList.ItemsSource = null;
CTransactionList.ItemsSource = ctransactionList;
This way, you are clearing the existing the databinding and enforcing the new list into the ListBox.
The other and suggestible fix is,
"Change your List to ObservableCollection. Because, ObservableCollection extends the INotifyPropertyChanged and hence has the ability to auto update the ListBox"
List<CTransaction> ctransactionList = new List<CTransaction>();//Change this to below
ObservableCollection<CTransaction> ctransactionList = new ObservableCollection<CTransaction>();//Define my collection
But a breakpoint before
CTransactionList.ItemsSource = ctransactionList;
And run the function twice. Do you have two items in ctransactionList now?
I suspect the binding fails and ctransactionList are in fact increasing
I am having troubles with dynamically adding a PivotItem to a templated and databound Pivot.
The classes in use (a bit simplified to keep it quickly comprehensable);
class Menu {
string Name
List<Dish> Dishes_1;
List<Dish> Dishes_2;
List<Dish> Dishes_3;
}
class Dish {
string Description
string Price;
}
I want to use a Pivot to display a list of Menu-Objects. I create PivotItems dynamically based on the number of items in that list. Each PivotElement should thus follow the same layout and behave the same. The lay-out template and databinding is done in the .xaml as following;
<phone:Pivot x:Name="Mainpivot">
<phone:Pivot.HeaderTemplate>
<DataTemplate>
<TextBlock Text="{Binding Name}" />
</DataTemplate>
</phone:Pivot.HeaderTemplate>
<phone:Pivot.ItemTemplate>
<DataTemplate>
<ListBox>
<TextBlock Text="Dishes_1"/>
<ListBox ItemsSource="{Binding Dishes_1}">
<ListBox.ItemTemplate>
<DataTemplate>
<Grid>
<TextBlock Text="{Binding Description}"/>
<TextBlock Text="{Binding Price}"/>
</Grid>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
// ...
// this is repeated 3 times;
//a textblock and listbox per List<Dishes> in the Menu-class
</ListBox>
</DataTemplate>
</phone:Pivot.ItemTemplate>
</phone:Pivot>
I populate the Pivot in de .cs file with the following:
foreach (Menu m in List_Menus) {
PivotItem p = new PivotItem();
p.DataContext = m;
Mainpivot.Items.Add(p);
}
As I set the DataContext as a Menu-Object, the DataBinding (through xaml) should not require any more code here (so I think?).
The problem now being; it doesn't work...
Through looking with the debugger, It appears that the created PivotItem doesn't behave like the template defined in Mainpivot tells (or so I think). Looking at Mainpivot does show that PivotItems have been added, but that's it, I believe they're just empty all-null PivotItems. When executing in the emulator, It just shows an empty Pivot.
Any thoughts?
//PS: I don't use ViewModels, as I find them quite confusing (as a beginner) as concept. I don't think that has anything to do with the problem though?
A few things here. First, for your binding to work you'll need to use properties instead of fields.
public class Menu {
public string Name {get;set;}
public List<Dish> Dishes_1 { get; set; }
public List<Dish> Dishes_2 { get; set; }
public List<Dish> Dishes_3 { get; set; }
}
public class Dish {
public string Description { get; set; }
public string Price { get; set; }
}
Next, instead of your foreach loop to add the items to the pivot just set the items source:
Mainpivot.ItemsSource = List_Menus;
BTW - you really should look into learning MVVM. It is worth the time.
My requirement as follows
1) Select the contact from the Contact ( select one or many)
2) Add the Mobile no and Contact name from selected contact in ListBox
3) To delete : Select a contact in the ListBox and delete dynamically and refresh data
My Problems:
a) How to I bind data from contact to ListBox?
PhoneCtc = new PhoneNumberChooserTask();
PhoneCtc.Completed += new EventHandler(PhoneCtc_Complete);
public void PhoneCtc_Complete(object sender, PhoneNumberResult e)
{
string No = e.PhoneNumber;
string Name = e.DisplayName;
// how to add this data dynamically to listbox and display ?
}
Thanks
Define the DataTemplate of the items in the listbox as shown below:
<ListBox Name="listBox1">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal" Height="132">
<TextBlock Text="{Binding No}"/>
<TextBlock Text="{Binding Name}"/>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
Then you'll have to create a class Contact containing No and Name:
public class Contact
{
public string No { get; set; }
public string Name { get; set; }
}
Now in code when you want to add an element, you just need to initialize an instance class Contact and set variables then write listBox1.Items.Add(Contact);
I'm trying the MVVM Light Toolkit. Though I still think having multiple ViewModels for such small apps is overkill, I like the concepts. What I still can't quite understand is how (or I should say "what is the recommended way") to navigate from one page to another when the selection changes in a ListBox.
The big problem with this toolkit is that it forces you to learn MVVM via other sources before using it, rather than show you what (its vision of) MVVM is from within the framework, accompanying samples and documentation. Are there samples out there showing the different concepts? And please, no videos.
Have you tried modifying your ListBox ItemTemplate to have each item be a HyperlinkButton and just setting the NavigateURI attribute to the Page you want to navigate to?
I still have not figured out how to do this (navigate to a details page upon selection changed in a listbox) without any codebehind in the view. However, if you are OK with having just a little codebehind in the view here's what I recommend:
<ListBox x:Name="MainListBox" Margin="0,0,-12,0" ItemsSource="{Binding Items}"
SelectionChanged="MainListBox_SelectionChanged"
SelectedItem="{Binding Path=SelectedListItem, Mode=TwoWay}">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Margin="0,0,0,17" Width="432">
<TextBlock Text="{Binding LineOne}" TextWrapping="Wrap" Style="{StaticResource PhoneTextExtraLargeStyle}"/>
<TextBlock Text="{Binding LineTwo}" TextWrapping="Wrap" Margin="12,-6,12,0" Style="{StaticResource PhoneTextSubtleStyle}"/>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
First, per above bind to the SelectedItem property of the Listbox with a TwoWay binding to a property in your ViewModel (SelectedListItem in the above).
Then in your codebehind for this page implement the handler for MainListBox_SelectionChanged:
// Handle selection changed on ListBox
private void MainListBox_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
// If selected index is -1 (no selection) do nothing
if (MainListBox.SelectedIndex == -1)
return;
// Navigate to the new page
NavigationService.Navigate(new Uri("/DetailsPage.xaml", UriKind.Relative));
}
This is the only codebehind you need in your main view.
In your main ViewModel you need a SelectedListItem property:
public const string SelectedListItemPropertyName = "SelectedListItem";
private ItemViewModel _SelectedListItem;
/// <summary>
/// Sample ViewModel property; this property is used in the view to display its value using a Binding
/// </summary>
/// <returns></returns>
public ItemViewModel SelectedListItem
{
get
{
return _SelectedListItem;
}
set
{
if (value != _SelectedListItem)
{
_SelectedListItem = value;
RaisePropertyChanged(SelectedListItemPropertyName);
}
}
}
Now, the trick to getting the context passed to your details page (the context being what list item was selected) you need to setup the DataContext in your Details view:
public DetailsPage()
{
InitializeComponent();
if (DataContext == null)
DataContext = App.ViewModel.SelectedListItem;
}
Hope this helps.
eventually you'll want to do more than just navigate, potentially navigate after setting a custom object.
Here is a MVVM-light way of doing this.
You'll first want to bind your listbox selected item to a property in your viewmodel
<ListBox ItemsSource="{Binding Events}" Margin="0,0,-12,0" SelectedItem="{Binding SelectedEvent, Mode=TwoWay}">
Declare your SelectedEvent property
public const string SelectedEventPropertyName = "SelectedEvent";
private Event _selectedEvent;
public Event SelectedEvent
{
get {return _selectedEvent;}
set
{
if (_selectedEvent == value)
{
return;
}
var oldValue = _selectedEvent;
_selectedEvent = value;
// Update bindings and broadcast change using GalaSoft.MvvmLight.Messenging
RaisePropertyChanged(SelectedEventPropertyName, oldValue, value, true);
}
}
You can then define an interaction trigger bound to the tap event
<i:Interaction.Triggers>
<i:EventTrigger EventName="Tap">
<cmd:EventToCommand Command="{Binding EventPageCommand, Mode=OneWay}"/>
</i:EventTrigger>
</i:Interaction.Triggers>
In your viewmodel, define your EventPageCommand as a RelayCommand:
public RelayCommand EventPageCommand { get; private set; }
public MainViewModel()
{
EventPageCommand = new RelayCommand(GoToEventPage);
}
and finally declare your GoToEventPage method
private void GoToEventPage()
{
_navigationService.NavigateTo(new Uri("/EventPage.xaml", UriKind.Relative));
}
note that you can do other actions before navigating to your new page, plus your selected item from your list box is currently set in the property you bound it too.