Adding list items to observable collection list - c#

Hi I am trying to add list items to an observable collection list.
I have a model where I setup a list property
public class DisplayList
{
public List<string> listItem { get; set; }
}
then on my main page I have an observable collection
private ObservableCollection<DisplayList> ListDisplay;
which I instantiate on page load
public MainPage()
{
this.InitializeComponent();
location = new ObservableCollection<storeLocations>();
ListDisplay = new ObservableCollection<DisplayList>();
// location = manager.getStoreLocations();
var dbList = db.Bales.Where(b => b.Location != null).Select(b => b.Location).ToList();
InitialLoad(dbList, null);
}
I am using suggestion boxes and want to filter results based on the selection made. The filtered results then display in a list on screen and this is where I am having a bit of trouble. I get it to display on screen, but it is displaying
System.Collection.Generic.List'1[S....... instead of the actual item in the list.
I am thinking I am not enumerating properly, but cant seem to pin point the error in my ways.
This is the method that is meant to populate the list based on selection of suggestion box.
public ObservableCollection<DisplayList>BaleList(List<string> CatNo)
{
foreach (var item in CatNo)
{
ListDisplay.Add(new DisplayList {listItem = CatNo.ToList()});
}
lstBales.IsItemClickEnabled = true;
return ListDisplay;
}
it takes in a parameter of type list which is gotten from the suggestion box. so the parameter value is basically what I want to display in the list on screen. e.g. CP1354-2 and second item CP1355-3 So those values come into the method. I want to apply those values to the observable collection as the listbox control is bound to the observable collection.
EDIT
adding binding in XAML
<ListView x:Name="lstBales" ItemsSource="{x:Bind ListDisplay}">
<ListView.ItemTemplate>
<DataTemplate x:Name="TemplateListName" x:DataType="data:DisplayList">
<Grid>
<TextBlock Text="{x:Bind listItem}"/>
</Grid>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>

ListDisplay.Add(new DisplayList {listItem = CatNo.ToList()});
and <TextBlock Text="{x:Bind listItem}"/>
You are binding a list to a TextBlock due to which ToString()is implicitly called and you don't get the actual values. According to your requirements, you can either change listItem to a string or create a nested ListView

Related

Organize Items in DropDownButton?

I've a collection of items inside an ObservableCollection, each item have a specific nation name (that's only a string). This is my collection:
private ObservableCollection<League> _leagues = new ObservableCollection<League>();
public ObservableCollection<League> Leagues
{
get
{
return _leagues;
}
set
{
_leagues = value;
OnPropertyChanged();
}
}
the League model have only a Name and a NationName properties.
The Xaml looks like this:
<Controls:DropDownButton Content="Leagues" x:Name="LeagueMenu"
ItemsSource="{Binding Leagues}"
ItemTemplate="{StaticResource CombinedTemplate}" >
<Controls:DropDownButton.GroupStyle>
<GroupStyle>
<GroupStyle.HeaderTemplate>
<DataTemplate>
<TextBlock Text="{Binding NationName}" />
</DataTemplate>
</GroupStyle.HeaderTemplate>
</GroupStyle>
</Controls:DropDownButton.GroupStyle>
</Controls:DropDownButton>
but I doesn't get any header for the NationName property, the items inside the DropDown are organized without header but as list, so without organization.
I'm trying to get this predisposition.
What am I doing wrong?
Preliminaries
Grouping items in an ItemsControl in WPF (which DropDownButton derives from) is fairly simple, and is accomplished in two steps. First you need to set up the items source by tweaking an ICollectionView associated with the source collection. Then you need to populate the ItemsControl.GroupStyle collection with at least one GroupStyle item - otherwise the items are presented in a plain (non-grouped) manner.
Diagnosis
The main issue you're facing is getting the drop-down to present the items in a grouped manner. Unfortunately, unlike setting up the items source, it is not something that is easily accomplished in case of the DropDownButton control. The reason for that stems from the way the control (or, more precisely, its template) is designed - the drop-down is presented inside a ContextMenu attached to a Button which is part of the template (see MahApps.Metro source code). Now ContextMenu also derives from ItemsControl, and most of its properties are bound to corresponding properties of the templated DropDownButton. That is however not the case for its GroupStyle property, because it's a read-only non-dependency property, and cannot be bound or event styled. That means that even if you add items to DropDownButton.GroupStyle collection, the ContextMenu.GroupStyle collection remains empty, hence the items are presented in non-grouped manner.
Solution (workaround)
The most reliable, yet most cumbersome solution would be to re-template the control and add GroupStyle items directly to the ContextMenu.GroupStyle collection. But I can offer you a much more concise workaround.
First of all, let's deal with the first step - setting up the items source. The easiest way (in my opinion) is to use CollectionViewSource in XAML. In your case it would boil down to something along these lines:
<mah:DropDownButton>
<mah:DropDownButton.Resources>
<CollectionViewSource x:Key="LeaguesViewSource" Source="{Binding Leagues}">
<CollectionViewSource.GroupDescriptions>
<PropertyGroupDescription PropertyName="NationName" />
</CollectionViewSource.GroupDescriptions>
</CollectionViewSource>
</mah:DropDownButton.Resources>
<mah:DropDownButton.ItemsSource>
<Binding Source="{StaticResource LeaguesViewSource}" />
</mah:DropDownButton.ItemsSource>
</mah:DropDownButton>
Now for the main part - the idea is that we'll create a helper class that will contain one attached dependency property that will assign an owner DropDownButton control to the ContextMenu responsible for presenting its items. Upon changing the owner we'll observe its DropDownButton.GroupStyle collection and use ContextMenu.GroupStyleSelector to feed the ContextMenu with items coming from its owner's collection. Here's the code:
public static class DropDownButtonHelper
{
public static readonly DependencyProperty OwnerProperty =
DependencyProperty.RegisterAttached("Owner", typeof(DropDownButton), typeof(DropDownButtonHelper), new PropertyMetadata(OwnerChanged));
public static DropDownButton GetOwner(ContextMenu menu)
{
return (DropDownButton)menu.GetValue(OwnerProperty);
}
public static void SetOwner(ContextMenu menu, DropDownButton value)
{
menu.SetValue(OwnerProperty, value);
}
private static void OwnerChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var menu = (ContextMenu)d;
if (e.OldValue != null)
//unsubscribe from the old owner
((DropDownButton)e.OldValue).GroupStyle.CollectionChanged -= menu.OwnerGroupStyleChanged;
if (e.NewValue != null)
{
var button = (DropDownButton)e.NewValue;
//subscribe to new owner
button.GroupStyle.CollectionChanged += menu.OwnerGroupStyleChanged;
menu.GroupStyleSelector = button.SelectGroupStyle;
}
else
menu.GroupStyleSelector = null;
}
private static void OwnerGroupStyleChanged(this ContextMenu menu, object sender, NotifyCollectionChangedEventArgs e)
{
//this method is invoked whenever owners GroupStyle collection is modified,
//so we need to update the GroupStyleSelector
menu.GroupStyleSelector = GetOwner(menu).SelectGroupStyle;
}
private static GroupStyle SelectGroupStyle(this DropDownButton button, CollectionViewGroup group, int level)
{
//we select a proper GroupStyle from the owner's GroupStyle collection
var index = Math.Min(level, button.GroupStyle.Count - 1);
return button.GroupStyle.Any() ? button.GroupStyle[index] : null;
}
}
In order to complete the second step we need to bind the Owner property for the ContextMenu (we'll use DropDownButton.MenuStyle to do that) and add some GroupStyle items to the DropDownButton:
<mah:DropDownButton>
<mah:DropDownButton.MenuStyle>
<Style TargetType="ContextMenu" BasedOn="{StaticResource {x:Type ContextMenu}}">
<Setter Property="local:DropDownButtonHelper.Owner" Value="{Binding RelativeSource={RelativeSource TemplatedParent}}" />
</Style>
</mah:DropDownButton.MenuStyle>
<mah:DropDownButton.GroupStyle>
<GroupStyle />
</mah:DropDownButton.GroupStyle>
</mah:DropDownButton>
This I think should be enough to achieve your goal.
If you check out the other post you've linked to, the answer has it all - in particular you need to bind to a CollectionView, rather than directly to the collection. Then you can set up grouping on the CollectionView.
So, in your case, define the property:
public ICollectionView LeaguesView { get; private set; }
and then after you've created your Leagues Collection, attach the View to your collection, and while you're at it set up the grouping on the view:
LeaguesView = (ListCollectionView)CollectionViewSource.GetDefaultView(Leagues);
LeaguesView.GroupDesriptions.Add(new PropertyGroupDescription("NationName"));
Then, bind your DropDownButton ItemSource to LeaguesView, and change your HeaderTemplate to bind to "Name" - which is the the name of the group:
<GroupStyle.HeaderTemplate>
<DataTemplate>
<TextBlock Text="{Binding Name}" />
</DataTemplate>
</GroupStyle.HeaderTemplate>
You can also use the ItemCount property in there if you want to show how many items there are in the group.

Listbox databinding NewItemPlaceholder

I have an observable collection bound to a list box.
The collection has 2 items, but the list box is showing 3 items (e.g. the 2 items that are actually in the observable collection and an additional item for the NewItemPlaceholder.
I want it only to show the 2 items.
Below is my XAML.
<ListBox MinHeight="20" MinWidth="20" Name="MultipleSelectionsMultipleWagersListBox" Visibility="{Binding Path=Coupon.BarcodeText, Converter={StaticResource CouponBarcodeToVisibilityConverter1}, ConverterParameter=994450_994550}" Height="AUto" Width="Auto" VerticalAlignment="Stretch" HorizontalAlignment="Stretch" Margin="5"
ItemsSource="{Binding Path=BetViewModels}" Grid.Row="1" Grid.Column="1" >
<ListBox.ItemTemplate>
<DataTemplate>
<View:BetView DataContext="{Binding}" Name="ThisBet" Margin="5"/>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
Here is the c#
private ObservableCollection<BetViewModel> _betViewModels = new ObservableCollection<BetViewModel>();
public ObservableCollection<BetViewModel> BetViewModels
{
get { return _betViewModels; }
set
{
if (Equals(value, _betViewModels)) return;
_betViewModels = value;
OnPropertyChanged("BetViewModels");
}
}
Here is the code to populate the betViewModels:
var betViewModel = new BetViewModel { Bet = new Bet() };
betViewModel.Bet.SelectionName = "Chelsea";
betViewModel.Bet.Price = "4/9";
betViewModel.Bet.Market = "90 Minutes";
betViewModel.Bet.ExpectedOdd = DateTime.Now;
BetViewModels.Add(betViewModel);
betViewModel = new BetViewModel { Bet = new Bet() };
betViewModel.Bet.SelectionName = "Chelsea";
betViewModel.Bet.Price = "4/9";
betViewModel.Bet.Market = "90 Minutes";
betViewModel.Bet.ExpectedOdd = DateTime.Now;
BetViewModels.Add(betViewModel);
How Do I switch of this from showing the additional item for the new item place
Here is an image of it displaying the placeholder
The DataGrid supports adding new rows, which have to start out blank. If your ItemsSource is bound to both a ListBox/ItemsControl and a DataGrid, you need to set the DataGrid 'CanUserAddRows' property to 'False'.
Where I found the answer: http://www.mindstick.com/Forum/1519/How%20do%20I%20remove%20a%20listbox%20new%20item%20placeholder
There's nothing in your code that should be adding an extra empty item. There may be some other code adding to BetViewModels or there may be a change happening to the generated ICollectionView for the collection if you have it bound to something else that you're not showing, like an editable DataGrid.
did your sample code also provide this issue?
how much items contains your _betViewModels.count in debugging there are really only 2 Items?
it seems you added an empty BetViewModel at the End
i would suggest check your logic which provides populates your items
if it is a loop it should (counter<yourDatasource.Count) just for example

ListBox.ItemContainerGenerator.ContainerFromIndex returns null

I have a dataBinding ListBox in a Pivot section.
In another Pivot section i have a form to create new items for the ListBox.
When i add a new item to the ListBox i need to acces to the new ListBoxItem for find a TextBox control and modify the Text value, but
ListBoxItem lbItem = allItemsListBox.ItemContainerGenerator.ContainerFromIndex(itemIndex) as ListBoxItem;
Always returns null.
The problem seems to be that the ListBox is not visible, so the new ListBoxItem is virtual.
How can i resolve that?
Thanks.
Why not use data binding with DataTemplates instead of trying to modify the controls directly?
for example:
Code:
// Data binding class
public class Data
{
// Implement INofifyPropertyChanged
public string Text { get; set; }
}
// Code to bind it to Pivot
ObservableCollection<Data> list = new ObservableCollection<Data>();
// populate list
Pivot1.ItemsSource = list;
XAML:
<Pivot Name="Pivot1">
<Pivot.ItemTemplate>
<DataTemplate>
<TextBlock Text={Binding Text}" />
</DataTemplate>
</Pivot.ItemTemplate>
</Pivot>
Now, to change Text of a specific TextBlock, all you need to do is change a value of associated Data object to it from the list.

SelectedItem changing details which are then bound to previous selected item

I have some hierachical data in the form of regions which contain systems which contain entrances
My WPF application has a list of trips which have an entrance as an attribute. The UI is a split window with a ListControl and a 'details' control bound to the ListView.SelectedItem property as follows: (code edited for brevity, only relevant parts shown)
<local:ListView x:Name="listView"/>
<local:DetailsView DataContext="{Binding ElementName=listView, Path=SelectedItem}"/>
The details view consists of ComboBox for the attributes, amongst them:
<ComboBox Name="comboRegion" SelectionChanged="Region_Changed"
ItemsSource="{Binding ElementName=main, Path=Regions, Mode=OneWay}" DisplayMemberPath="Name"
SelectedItem="{Binding Region, Mode=OneWay}"/>
<ComboBox Name="comboSystem" SelectionChanged="System_Changed"
ItemsSource="{Binding ElementName=main, Path=Systems, Mode=OneWay}" DisplayMemberPath="Name"
SelectedItem="{Binding System, Mode=OneWay}"/>
<ComboBox ItemsSource="{Binding ElementName=main, Path=Entrances, Mode=OneWay}" DisplayMemberPath="Name"
SelectedItem="{Binding Enter}"/>
Trip.Enter is the attribute I want to edit, Trip.Region and Trip.System are read only and calculated from Trip.Enter.
main.Regions, main,systems and main.Entrances are lists local to the control which has the following code:
public IEnumerable<Region> Regions { get; private set; }
public IEnumerable<CaveSystem> Systems { get; private set; }
public IEnumerable<Entrance> Entrances { get; private set; }
private void Region_Changed(object sender, SelectionChangedEventArgs e)
{
Region = comboRegion.SelectedItem as Region;
Systems = (region != null ? region.Systems : null);
NotifyPropertyChanged("Systems");
}
private void System_Changed(object sender, SelectionChangedEventArgs e)
{
... Equivalent to Region_Changed except updates Entrances ...
}
The Regions list is static so is populated once.
When a new region is selected, the Systems list is repopulated with the new list
When a new system is selected (also as a cascade from changing region), the entrances list is repopulated.
So far, so good. This works as expected, selecting a trip in the list view binds its details to the combo boxes. Changing the value in a combo box, updates the appropriate "lower level" boxes with the new list.
Finally, selecting an entrance updates the record itself (list view updates appropriately)
The problem is:
When I select a new record in the list view, the values in the new record appear in the combo boxes, but are also copied to the last record selected.
I think the problem is that, changing the data context for the details view causes the bindings to all be updated. I think this happens in turn but changing the selected region causes a ripple effect in the lower level combo boxes which also change. I think they are, at that point, still bound to the old record.
Can anyone suggest a way round this?
You can try use strategy pattern. Dont change your datacontext, but chenge class in your datacontext. In this inner class you can have working logic but your datacontext will be same. Example:
public interface InnerDataContext
{
public CaveSystem System{get;}
}
public class DataContext
{
private InnerDataContext dc;
public CaveSystem
{ get{ return dc.System; }}
}
you can change your inner datacontext when you want and it wouldnt throw update values.
OK, found a solution!
I changed the bindings to
UpdateSourceTrigger=LostFocus
This means that the object is only updated if the user selects a new value in the combo box, Not when the combo box is updated due to changing the data context.

WPF Listbox with checkboxes appearing blank, added dynamically

I'm trying to populate a listbox with a series checkbox entries, however once running the code below the listbox has blank entries in it, which are selectable, i.e. a blue bar appears. However neither the text or checkbox appears.
for (int num = 1; num <= 10; num++)
{
CheckBox checkBox = new CheckBox();
checkBox.Text = "sheet" + num.ToString();
checkBox.Name = "checkbox" + num.ToString();
thelistbox.Items.Add(checkBox);
}
The best way to handle this is to create a list of data -- in your case, a list of numbers (or a list of strings (sheet1, sheet2, etc). You can then assign that list of numbers to thelistbox.ItemsSource. Inside the XAML of your listbox, set the ItemTemplate to include a CheckBox and bind the number to the text of the checkbox.
Try changing
checkBox.Text = "sheet" + num.ToString();
to
checkBox.Content = "sheet" + num.ToString();
With that change, I was able to use your example successfully.
To follow up on Brian's comment, here is an outline of a simple checkbox list in C# wpf. This will need more code to handle checking/unchecking boxes and general post-interaction handlers. This setup presents the difference in elements on two lists of objects (defined elsewhere) in a checkbox list.
The XAML
...
<ListBox Name="MissingNamesList" ItemsSource="{Binding TheMissingChildren}">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel>
<CheckBox Content="{Binding Path=Name}" />
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
...
The supporting C# code:
...
public partial class MissingNamesWindow : Window
{
// Make this accessible from just about anywhere
public ObservableCollection<ChildName> TheMissingChildren { get; set; }
public MissingNamesWindow()
{
// Build our collection so we can bind to it later
FindMissingChildren();
InitializeComponent();
// Set our datacontext for this window to stuff that lives here
DataContext = this;
}
private void FindMissingChildren()
{
// Initialize our observable collection
TheMissingChildren = new ObservableCollection<ChildName>();
// Build our list of objects on list A but not B
List<ChildName> names = new List<ChildName>(MainWindow.ChildNamesFromDB.Except(
MainWindow.ChildNamesFromDisk).ToList());
// Build observable collection from out unique list of objects
foreach (var name in names)
{
TheMissingChildren.Add(name);
}
}
}
...
Hope that clarifies a bit.

Categories