WPF Adding an object to ListBox with existing ItemsSource - c#

Hey I have a list box that I set the ItemsSource to be an ObservableCollection of objects from my database, and I need to add a single object at the end of this list. However I keep getting an invalid operation exception. Somehow my listbox is in use (which in my mind is a given as it is displayed and already have items inside.) Here is my code for the list box:
<ListBox x:Name="CarList" SelectionChanged="ItemSelected" ScrollViewer.HorizontalScrollBarVisibility="Disabled" Background="{x:Null}">
<ListBox.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel FlowDirection="LeftToRight" ItemHeight="300" ItemWidth="300"/>
</ItemsPanelTemplate>
</ListBox.ItemsPanel>
<ListBox.ItemTemplate>
<DataTemplate>
<Grid Margin="10,10">
<Grid.RowDefinitions>
<RowDefinition Height="*" />
<RowDefinition Height="35" />
</Grid.RowDefinitions>
<Image Grid.Row="0" Source="{Binding image_path}" VerticalAlignment="Stretch"/>
<Grid Grid.Row="1" Background="SteelBlue">
<TextBlock HorizontalAlignment="Left" VerticalAlignment="Center" Margin="3" Text="{Binding model}"/>
<TextBlock HorizontalAlignment="Right" VerticalAlignment="Center" Margin="3" Text="{Binding price}"/>
</Grid>
</Grid>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
And I first set the ItemsSource like so:
CarList.ItemsSource = CarController.GetAllCars();
And then want to add my custom object like this:
ListBoxItem carAdd = new ListBoxItem();
carAdd.Content = new CarModel{ image_path = "/../Assets/add-512.png", id=-1};
CarList.Items.Add(carAdd);
But alas the last operation fails with this message:
Operation is not valid while ItemsSource is in use. Access and modify
elements with ItemsControl.ItemsSource instead.
I have looked for a few other suggestions but all use strings and single bindings in their examples and thus I haven't been able to figure out what exactly to do. If anyone got a suggestion it would be much appreciated.
-
Thanks.

You need to add the item to the items source, and the source should be observable so that the ListBox takes the new item into account:
var cars = new ObservableCollection<CarModel>(CarController.GetAllCars());
CarList.ItemsSource = cars;
...
var car = new CarModel{ image_path = "/../Assets/add-512.png", id=-1};
cars.Add(car);

Related

ContentControl and CollectionView.CurrentItem

Referencing this example:
https://learn.microsoft.com/en-us/dotnet/framework/wpf/data/how-to-bind-to-a-collection-and-display-information-based-on-selection
(some relevant code snippets:)
<Window.Resources>
<local:People x:Key="MyFriends"></local:People>
<DataTemplate x:Key="DetailTemplate">
<Border Width="300" Height="100" Margin="20"
BorderBrush="Aqua" BorderThickness="1" Padding="8">
<Grid>
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition/>
<RowDefinition/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<TextBlock Grid.Row="0" Grid.Column="0" Text="First Name:"/>
<TextBlock Grid.Row="0" Grid.Column="1" Text="{Binding Path=FirstName}"/>
<TextBlock Grid.Row="1" Grid.Column="0" Text="Last Name:"/>
<TextBlock Grid.Row="1" Grid.Column="1" Text="{Binding Path=LastName}"/>
<TextBlock Grid.Row="2" Grid.Column="0" Text="Home Town:"/>
<TextBlock Grid.Row="2" Grid.Column="1" Text="{Binding Path=HomeTown}"/>
</Grid>
</Border>
</DataTemplate>
</Window.Resources>
<ListBox Width="200" IsSynchronizedWithCurrentItem="True"
ItemsSource="{Binding Source={StaticResource MyFriends}}"/>
<ContentControl x:Name="contentControl1"
Content="{Binding Source={StaticResource MyFriends}}"
ContentTemplate="{StaticResource DetailTemplate}" />
Both ListBox.ItemsSource and ContentControl.Content bind to the same source (MyFriends, an instance of the People class which derives from ObservableCollection<Person>). If my understanding is correct, this means that both the ListBox.ItemsSource and ContentControl.Content properties will be bound to the same implicitly created instance of ListCollectionView.
I understand that setting ListBox.IsSynchronizedWithCurrentItem="True" synchronizes ListBox.SelectedItem and ItemCollection.CurrentItem.
DetailTemplate (above) displays the details of the selected ListBox item, despite being 'bound' to a ListCollectionView. Specifying Path=/ (what I thought would be necessary to achieve the resulting behavior) does not have any effect - it's as if WPF knows to do it implicitly somehow:
<ContentControl x:Name="contentControl1"
Content="{Binding Source={StaticResource MyFriends}, Path=/}"
ContentTemplate="{StaticResource DetailTemplate}" />
As a test, I created another ContentControl with Content bound to a DataTemplate containing a ListBox:
<ContentControl x:Name="contentControl2"
Content="{Binding Source={StaticResource MyFriends}}"
ContentTemplate="{StaticResource DetailTemplate2}" />
<DataTemplate x:Key="DetailTemplate2">
<ListBox ItemsSource="{Binding}"></>
</DataTemplate>
And it displayed the list.
My question is: Why does DataTemplate get the selected Person object while the ListBox and DetailTemplate2 get the People collection?
(the behavior is desirable, I just don't understand what black magic is occurring under the hood to make it so)
Is a good question! I didn't notice that until read your post. So, after did some digging from source code of PropertyPathWorker, it appears that when PropertyPathWorker failed to solve a member of an object, in your case, it try to solve 'FirstName', 'LastName' ect. with 'MyFriends', it will try to solve it with the view of the object. And if still failed, it will try to solve it with view's CurrentItem, and that's where the magic happened. You can find those codes in PropertyPathWorker.UpdateSourceValueState(int k, ICollectionView collectionView, object newValue, bool isASubPropertyChange) and PropertyPathWorker.ReplaceItem(int k, object newO, object parent).

How to take value from a label from WPF Itemscontrols

good afternoon, I am writing this question to see if you can help me with a problem that I am having, it is probably easy to solve but I have not been able to make it work for days.
WPF programming I created an itemscontrols where I list a list with all the books I have, until there everything works perfect, my problem is that I want to do that when I press a button I take the value of the name of the book to use it in another part.
Then I leave the fragment of code that I have so that I can understand a little more
<StackPanel Name="stkMain">
<ItemsControl Name="itmCntrl">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel Orientation="Horizontal" VerticalAlignment="Center" HorizontalAlignment="Center" ></WrapPanel>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<StackPanel Name="stk">
<materialDesign:Card Width="300" Margin="10" VerticalAlignment="Stretch">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="*" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Image Name="img" Source="{Binding PhotoPath}" Height="300" Width="240" Stretch="Fill" Cursor="Hand" />
<Button Grid.Row="1" Grid.Column="1" Style="{DynamicResource MaterialDesignFlatButton}" Content="MORE"
HorizontalAlignment="Right" Margin="8" Click="Button_Click"/>
<Label x:Name="nan" Content="{Binding Name}"></Label>
</Grid>
</materialDesign:Card>
</materialDesign:TransitioningContent>
</StackPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</StackPanel>
As you can see for each item created I have an image, a label and a button, and basically what I can not do is that by pressing the button I take the value of the label
I hope I can lend a hand with this.
Thank you.
in a Button_Click method sender parameter is a Button which has DataContext ptoperty. DataContext is a single object with Name property:
// c#
Button b = (Button)sender;
object dc = b.DataContext;
//// cast dc to correct type, e.g.
// Book book = (Book)dc;
// string name = book.Name;

Hub with dynamic HubSections with GridViews and DataBinding

I would like to create a Hub with several HubSections via code. Each HubSection owns a single GridView so it look like every HubSection is a table (fullscreen) and I swipe left/right to view every table.
In my XAML page is only Hub the other stuff should be done by code. The HubSections should be created at runtime. For this I use a local settings storage to save some information about this, like how many HubSections etc.
Creating new HubSections is no problem but I'm stuck at adding a GridView to each HubSection because I don't understand the logic here. It looks like I have to add a DataTemplate and a GridView but my attempts all failed.
Note: each GridView has also it's own databinding from a Observable Collection.
So how to add a (?DataTemplate?) GridView with databinding to a HubSection ?
With a DataTemplate you build your layout. I have used in a Project following template to show a few data per day and create one Section for each day:
<Page.Resources>
<CollectionViewSource x:Name="HubViewModel"/>
<DataTemplate x:Key="DataTemplate">
<Grid Background="Transparent" Width="300" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Margin="0,0,0,20">
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<StackPanel Grid.Row="0" HorizontalAlignment="Center">
<TextBlock Text="{Binding SumShipmentsSA}" Style="{ThemeResource HeaderTextBlockStyle}" TextAlignment="Center" TextWrapping="NoWrap"/>
</StackPanel>
<StackPanel Grid.Row="1" HorizontalAlignment="Center">
<StackPanel Orientation="Horizontal">
<TextBlock x:Uid="SummaryHubNat" Text="National" FontSize="10" Width="100" VerticalAlignment="Center" Margin="0,0,20,0"/>
<TextBlock Text="{Binding CountShipmentsNationalSA}" Style="{ThemeResource BodyTextBlockStyle}" TextWrapping="NoWrap"/>
</StackPanel>
<StackPanel Orientation="Horizontal">
<TextBlock x:Uid="SummaryHubInt" Text="International" FontSize="10" Width="100" VerticalAlignment="Center" Margin="0,0,20,0"/>
<TextBlock Text="{Binding CountShipmentsInternationalSA}" Style="{ThemeResource BodyTextBlockStyle}" TextWrapping="NoWrap"/>
</StackPanel>
<StackPanel Orientation="Horizontal">
<TextBlock x:Uid="SummaryHubCharter" Text="Charter" FontSize="10" Width="100" VerticalAlignment="Center" Margin="0,0,20,0"/>
<TextBlock Text="{Binding CountShipmentsCharterSA}" Style="{ThemeResource BodyTextBlockStyle}" TextWrapping="NoWrap"/>
</StackPanel>
</StackPanel>
</Grid>
</DataTemplate>
</Page.Resources>
.
.
<Hub x:Name="MainHub" DataContext="{Binding Source={StaticResource HubViewModel}}" Margin="0,0,0,20"/>
In the Code page I used the following method to create to the Section:
private void AddHubSection(IEnumerable<DaySummary> list)
{
if (list != null)
{
list = list.OrderByDescending(x => x.Date);
foreach (var item in list)
{
if (item.Date.Date.Equals(DateTime.Now.Date))
{
continue;
}
HubSection hubSection = new HubSection();
TextBlock headerTextBlock = new TextBlock();
headerTextBlock.Text = item.Date.ToString("dddd dd.MMM");
headerTextBlock.FontSize = 15;
hubSection.Header = headerTextBlock;
hubSection.Margin = new Thickness(0);
object dataTemplate;
this.Resources.TryGetValue("DataTemplate", out dataTemplate);
hubSection.ContentTemplate = dataTemplate as DataTemplate;
hubSection.DataContext = item;
hubSection.DoubleTapped += HubSection_DoubleTapped;
MainHub.Sections.Add(hubSection);
}
}
}
I think the example can help you have fun while trying.

Ungrouped Long List Selector

Okay, So I am trying to bind a list of objects with a DisplayName property to a long list selector.
XAML Code
<phone:LongListSelector x:Name="lls_TemplateFields" HorizontalAlignment="Left" Width="450" Grid.Row="2" Height="400" LayoutMode="List" Background="#FF9E9D9D" IsGroupingEnabled="False">
<phone:LongListSelector.ItemTemplate>
<DataTemplate>
<Grid>
<TextBlock Text="{Binding DisplayName}" Foreground="Black" FontSize="24"/>
</Grid>
</DataTemplate>
</phone:LongListSelector.ItemTemplate>
</phone:LongListSelector>
C# Code
List<AttributeDefinition> m_SelectedAttributes = new List<AttributeDefinition>();
lls_TemplateFields.ItemsSource = m_SelectedAttributes;
My Class AttributeDefinition contains a property for DisplayName. If I group the the list using a group key then the list will show up, however I cannot get just a plain list of items to show up. Like a listbox in WPF C#.
I am using this list to represent a list of chosen AttributeDefinitions from another list that shows all the AttributeDefinitions grouped alphabetically by their DisplayName property, and the Display Value is Binded to the DisplayName Property like shown below...
XAML
<phone:LongListSelector x:Name="lls_AttributeList" HorizontalAlignment="Left" Height="450" VerticalAlignment="Top" Width="450" HideEmptyGroups="True" IsGroupingEnabled="True" SelectionChanged="SelectionChanged">
<phone:LongListSelector.ItemTemplate>
<DataTemplate>
<Grid>
<Border BorderThickness="2">
<TextBlock Text="{Binding DisplayName}" Foreground="{StaticResource PhoneChromeBrush}" FontSize="24"/>
</Border>
</Grid>
</DataTemplate>
</phone:LongListSelector.ItemTemplate>
<phone:LongListSelector.GroupHeaderTemplate>
<DataTemplate>
<Grid Width="50" Height="50" HorizontalAlignment="Left" VerticalAlignment="Center" Background="Blue">
<Border BorderThickness="4">
<TextBlock Text="{Binding Key}" Foreground="White" FontSize="38" HorizontalAlignment="Center" VerticalAlignment="Center" />
</Border>
</Grid>
</DataTemplate>
</phone:LongListSelector.GroupHeaderTemplate>
</phone:LongListSelector>
C#
List<AttributeKeyGroup<AttributeDefinition>> DataSource = AttributeKeyGroup<AttributeDefinition>.CreateGroups(AttributeData,
Thread.CurrentThread.CurrentUICulture,
(AttributeDefinition aDef) => { return aDef.Type; },
true);
lls_AttributeList.ItemsSource = DataSource;
This list works just fine. And the data in the background is functioning properly, because as I select items from the total list of attributedefinitions they are removed from the LongListSelector and added to the LongListSelector of the selected lists Itemssource
For further inquiry into more code surrounding this User Control please feel free to ask I will disclose as much code as I can without breaking my non-disclosure agreement with my work. Thank you for taking the time to read and possibly help. Much appreciated.
List<AttributeDefinition> m_SelectedAttributes = new List<AttributeDefinition>();
lls_TemplateFields.ItemsSource = m_SelectedAttributes;
because you just instantiated this list and binding it right away? How about adding some items into the list
There are two answers to this. I have tried both and both work.
[1] You have to set the LongListSelector.ScrollTo(object) to the first object of your list when you update the data if the list has 1 or more items. Such as...
lls_TemplateFields.ItemsSource = m_SelectedAttributes;
if(m_SelectedAttributes.Count > 0)
{
lls_TemplateFields.ScrollTo(m_SelectedAttributes[0]);
}
[2] The proper way to fix this is to use ObservableCollections which work better with DataBinding and the WP8 WPF SDK UI to update on adding objects to the ObservableCollection.
ObservableCollection<AttributeDefinition> m_SelectedAttributes =
new ObservableCollection<AttributeDefinition>();
lls_TemplateFields.ItemsSource = m_SelectedAttributes

ListBox ItemsSource Binding doesn't work

I'm developing a Windows Phome application. I have the following ListBox on a page:
<ListBox Margin="10,10,8,8" x:Name="WallList">
<ListBox.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel />
</ItemsPanelTemplate>
</ListBox.ItemsPanel>
<ListBox.ItemTemplate>
<DataTemplate>
<Grid x:Name="ListBoxItemLayout" Background="Transparent" Margin="10">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="0.33*"/>
<ColumnDefinition Width="0.77*"/>
</Grid.ColumnDefinitions>
<Image HorizontalAlignment="Left" Margin="0" Source="{Binding ImagePath}" Height="200"/>
<StackPanel Margin="5,0,0,0" Grid.Column="1">
<TextBlock x:Name="Name" TextWrapping="Wrap" Text="{Binding Name}" Style="{StaticResource PhoneTextTitle2Style}"/>
<TextBlock x:Name="Comment" Margin="0,5,0,0" TextWrapping="Wrap" Text="{Binding Comment}" Style="{StaticResource PhoneTextNormalStyle}" Height="130"/>
<TextBlock x:Name="When" TextWrapping="Wrap" Text="{Binding When}" Style="{StaticResource PhoneTextTitle3Style}" VerticalAlignment="Bottom"/>
</StackPanel>
</Grid>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
I'm using this to fill the ListBox on Loaded event:
this.WallList.ItemsSource = StartingWall.GetWallPosts();
And now I want to add more items programmatically when the user write down some text on a TextBox and click on a button. I want to place this text on Comment field.
I'm gooing to fill the rest fields with default data.
My question is:
How can I add more items to WallList ListBox?
Someone has suggested to do the following:
public ObservableCollection<WallPostEntry> MyWallPosts {get;set;}
// Initialize MyWallPosts to whatever
MyWallPosts.Add(new WallPostEntry("new entry"));
<ListBox Margin="10,10,8,8" x:Name="WallList" ItemsSource="{Binding MyWallPosts}">
But Binding ListBox ItemsSource doesn't work for me. I'm initializing MyWallPosts on constructor, just before InitializeComponent();, like this:
public Wall()
{
MyWallPosts = StartingWall.GetWallPosts();
InitializeComponent();
}
Any advice?
Thanks.
I see a couple wierd things:
first, you're using the itemssource binding in one place, but explicitly setting it in another? setting something in code will override/undo any bindings, so that could cause a problem (but it looks like you're setting it to the same thing so that shouldn't make a difference, but i'd remove the this.WallList.ItemsSource = StartingWall.GetWallPosts(); call entirely, and leave the ItemsSource="{Binding MyWallPosts}" in the xaml. the point of using bindings is to get rid of this kind of code)
second, you're setting mywallposts and using a binding, but not setting the datacontext on your object itself? simplest in your example would be to just add one line to your constructor:
public Wall()
{
DataContext = this;
MyWallPosts = StartingWall.GetWallPosts();
InitializeComponent();
}
My next suggestion would be to simplify until it works. leave the listbox but comment out all of the item/data templating to make sure that you don't have a bug in your template
Could he just add
a DataContext property to this:
<ListBox Margin="10,10,8,8" x:Name="WallList" ItemsSource="{Binding MyWallPosts}">
so:
<ListBox Margin="10,10,8,8" x:Name="WallList" ItemsSource="{Binding MyWallPosts}" DataContext="{Binding MyWallPosts}">
Would there be any way of setting the dataContext declarativelly?
tks,
Oscar

Categories

Resources