WPF binded Listbox bug when display 2 items - wpf

I have a WPF listbox in my window. In the Load event of the window, i create a List(of Object) and I added some items. At application starts or debug, I can see items.
If I add 1 item on the list, i correctly see 1 only item. If I add 3 or more items, i correctly see 3 or more items. If I add 2 only items, i see 1 only item. Why?
Here is my WPF code
<Window x:Class="Cacatua.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:Cacatua;assembly=" >
<Grid>
<ListBox Name="lbSearch" ItemsSource="{Binding}" />
</Grid>
</Window>
And here is my code-behind (same assembly, in Cacatua namespace):
Private myLstSearch As List(Of Object)
Private Sub Window_Loaded(sender As System.Object, e As System.Windows.RoutedEventArgs) Handles MyBase.Loaded
myLstSearch = New List(Of Object)
lbSearch.ItemsSource = myLstSearch
Dim myMedia1 as Media1
myMedia1 = New Media1("IdMedia1-A")
myLstSearch.Add(myMedia1)
myMedia1 = New Media1("IdMedia1-B")
myLstSearch.Add(myMedia1)
End Sub
where Media1 is a simple class that contains a string
Public Class Media1
Private myIdTitolo As String
Public ReadOnly Property IDTitolo As String
Get
Return (myIdTitolo)
End Get
End Property
Public Sub New(str As String)
myIdTitolo = str
End Sub
End Class
With this code, I would see a list with this output (there is no datatemplate):
Cacatua.Media1
Cacatua.Media1
but I see only
Cacatua.Media1
I think it's a bug. But am I the first with this problem?

You've got the right idea, but the problem is your ItemsSource doesn't know when to update since you're not using an ObservableCollection. Also there is a timing issue between rendering and loading the window, and I think this has to do with the fact you aren't properly binding your items source.
For starters, try changing the type of myLstSearch to ObservableCollection(Of Media1).
Also, a better way to do this would be to databind it from the XAML directly, so your code-behind would be something like:
Public property MyListSearch As ObservableCollection(Of Media1)
Then your XAML would look like:
<Window x:Class="Cacatua.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:Cacatua;assembly=">
<Grid>
<ListBox Name="lbSearch" ItemsSource="{Binding Path=MyListSearch}" />
</Grid>
</Window>
That way, you can simply initialize MyListSearch in your window constructor, and then add elements to it whenever, while your view will automatically update.

Related

WPF Combobox Item Update

I am rather new to the WPF setup and I am running into an issue where as far as I can see I have set it up correctly to have my combobox bound to a observable collection of object.
The Combobox will update when I add or delete items. If I make a change the items in the drop down will not show any differently but if I select one that was edited it will now show the new information but only when selected.
I have set up the object class to use INotifyPropertyChanged correctly I think but it does not seem to be functioning. Going to attach the code below so that you can easily see exactly what I am trying to describe.
What I am trying to do it allow a user to push a button and have the text inside a combobox update to show the new text.
Imports System.ComponentModel
Public Class Window2
Public _names As New System.Collections.ObjectModel.ObservableCollection(Of TestClass)
Public Sub BaseLoading() Handles MyBase.Loaded
Dim AddNewItem As New TestClass
AddNewItem.groupName = "Item " + (_names.Count + 1).ToString
_names.Add(AddNewItem)
cbo_Names.SetBinding(ItemsControl.ItemsSourceProperty, New Binding With {.Source = _names})
End Sub
Private Sub button_PreviewMouseDown(sender As Object, e As MouseButtonEventArgs)
Dim AddNewItem As New TestClass
AddNewItem.groupName = "Item " + (_names.Count + 1).ToString
_names.Add(AddNewItem)
_names(0).groupName = ("Value Changed")
End Sub
End Class
Public Class TestClasss
Implements INotifyPropertyChanged
Public _groupName As String = ""
Public Property groupName As String
Get
Return _groupName.ToString
End Get
Set(value As String)
_groupName = value
onPropertyChanged(New PropertyChangedEventArgs(_groupName))
End Set
End Property
Public Event PropertyChagned(ByVal sender As Object, ByVal e As PropertyChangedEventArgs) Implements INotifyPropertyChanged.PropertyChanged
Public Sub onPropertyChanged(ByVal e As PropertyChangedEventArgs)
RaiseEvent PropertyChagned(Me, e)
End Sub
End Class
XAML
<Window x:Class="Window2"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525">
<StackPanel>
<Button x:Name="button" Content="Button" PreviewMouseDown="button_PreviewMouseDown"/>
<ComboBox x:Name="cbo_Names" Margin="30,5,30,5" IsEditable="False" ItemsSource="{Binding _names, NotifyOnSourceUpdated=True,Mode=OneWay, UpdateSourceTrigger=PropertyChanged}" DisplayMemberPath="groupName" SelectedItem="{Binding _names, NotifyOnSourceUpdated=True,Mode=OneWay, UpdateSourceTrigger=PropertyChanged}" />
</StackPanel>
</Window>
I would appreciate any help locating what I am missing.
You should pass the name of the data-bound property (instead of the value of the property) to the constructor of the PropertyChangedEventArgs:
onPropertyChanged(New PropertyChangedEventArgs("groupName"))
If you are using at least Visual Studio 2015, you could consider making the following change to your onPropertyChanged routine:
Public Sub onPropertyChanged(<System.Runtime.CompilerServices.CallerMemberName> Optional ByVal propertyName As String)
RaiseEvent PropertyChanged(Me, New PropertyChangedEventArgs(propertyName))
End Sub
Then, in the setter for groupName you can call onPropertyChanged without specifying the property name, and it will be taken from the name of the caller (that is, it will end up being "groupName").
Effectively, this is doing the same thing as the previous answer, but in a way that is easier for you to code and maintain. (Along with the <CallerMemberName> attribute, this works well with NameOf, both making your code more robust against any changes in names of properties.)

WPF INotifyPropertyChanged not updating label

I'm currently learning some basics in WPF and I've been looking for the mistake for about 2 days. Hope you guys can help.
I'm trying to update my UI (in this case the content of a label) by using INotifyPropertyChanged and a binding in XAML. The thing is: it only takes the first value and puts it in the content. Furthermore nothing happens but the event (OnPropertyChanged) is fired.
This is what I have in XAML:
<Window
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:WpfApplication1" x:Class="MainWindow"
Title="MainWindow" Height="350" Width="525">
<Window.Resources>
<local:View x:Key="ViewModel"/>
</Window.Resources>
<Grid Margin="0,0,2,-4" DataContext="{Binding Source={StaticResource ViewModel}}">
....
<Label x:Name="lbl_money" Grid.ColumnSpan="2" Content="{Binding Path=PropMoney}" HorizontalAlignment="Left" Margin="403,42,0,0" VerticalAlignment="Top">
And this is the necessary part of my class View:
Public Class View
Inherits ViewModelBase
Private rest1 As New Restaurant
Private mainPlayer As New Player
Private mycurrentMoney As Double = 3
Private currentClickIncrease = mainPlayer.PropClickIncrease
Public Property PropMoney() As Double
Get
Return mycurrentMoney
End Get
Set(value As Double)
mycurrentMoney = value
OnPropertyChanged("mycurrentMoney")
End Set
End Property
Sub SelfClicked()
PropMoney() += 1
End Sub
Last but not least the MainWindow class, where i instantiate my view:
Class MainWindow
Private view As New View
Sub New()
InitializeComponent()
End Sub
Private Sub Button_Click(sender As Object, e As RoutedEventArgs)
view.SelfClicked()
End Sub
End Class
So my mycurrentMoney is increasing each click and the event is fired but the label doesn't update.
Thank you in advance!
If you have Visual Studio 15 use NameOf operator instead of string literal like so:
NameOf(PropMoney);
If you later rename your property, it will still work opposed to string literal which will NOT. Alternatively modify your OnPropertyChange to make use of CallerMemberName
OnPropertyChange ([System.Runtime.CompilerServices.CallerMemberName] string memberName = "")
{
}
The property name will be filled in, this works only in setter for current property however.
Also, set DataContext for whole window (Setting DataContext in XAML in WPF). DataContext={StaticResource ViewModel} and don't use Path in your Binding, just {Binding PropertyName}
Your OnPropertyChanged("mycurrentMoney") statement won't raise a property change on your property, because it's called PropMoney.
You have to set OnPropertyChanged("PropMoney") in your setter instead.
There are 2 problems with your code
First you raise PropertyChanged event for the backing field and should raise it for property name
OnPropertyChanged("PropMoney")
Second the property you change belong to different instance of View then the one set as DataContext. So in XAML remove DataContext changes, only leave property binding
<Window ...>
<Grid Margin="0,0,2,-4">
<!-- .... -->
<Label ... Content="{Binding Path=PropMoney}">
and then in code set DataContext of MainWindow to the instance that you create and modify
Class MainWindow
Private view As New View
Sub New()
InitializeComponent()
DataContext = view
End Sub
Private Sub Button_Click(sender As Object, e As RoutedEventArgs)
view.SelfClicked()
End Sub
End Class

Selection changing event in WPF

I am looking for a way to prevent a selection change in WPF items (the Tab control right now, but in the future this will need to be done for ListBoxes, ListViews and ComboBoxes).
I came across this thread and attempted to use the same technique that was marked as the answer.
In that technique you retrieve the CollectionView for the tab control's items and handle the CollectionView's CurrentChanging event to prevent the selection from occurring.
For some reason, the CurrentChanging event is never being fired in my code.
Here is my very simple user control that I am working with.
It has a tab control with 3 tabs.
(XAML)
<UserControl x:Class="UserControlWithTabs"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300">
<TabControl x:Name="MainTabControl">
<TabItem Header="First Tab">Content for the first tab</TabItem>
<TabItem Header="Second Tab">Content for the second tab</TabItem>
<TabItem Header="Third Tab">Content for the third tab</TabItem>
</TabControl>
</UserControl>
In my VB.NET code for the user control, I am simply retrieving the CollectionView for the tab control's items and using the AddHandler method to watch for the event.
(VB.NET)
Public Class UserControlWithTabs
Private WithEvents mainTabCollectionView As CollectionView
Private Sub UserControlWithTabs_Loaded(sender As Object, e As System.Windows.RoutedEventArgs) Handles Me.Loaded
mainTabCollectionView = CollectionViewSource.GetDefaultView(MainTabControl.Items)
AddHandler mainTabCollectionView.CurrentChanging, AddressOf MainTabControl_ItemSelecting
End Sub
Private Sub MainTabControl_ItemSelecting(ByVal sender As Object, ByVal e As System.ComponentModel.CurrentChangingEventArgs)
End Sub
End Class
I put a break point on the MainTabControl_ItemSelecting method, but it is never hit.
What am I doing wrong?
Thanks,
-Frinny
Have you tried adding IsSynchronizedWithCurrentItem="True" to your TabControl?
Thanks to both the question and answer, i was able to do this in c#.
So for anyone needing something like this with c# code-behind, here's how i did it:
mytab.IsSynchronizedWithCurrentItem = true;
mytab.Items.CurrentChanging += new CurrentChangingEventHandler(Items_CurrentChanging);
private void Items_CurrentChanging(object sender, CurrentChangingEventArgs e)
{
if (e.IsCancelable)
{
FrameworkElement elemfrom = ((ICollectionView)sender).CurrentItem as FrameworkElement;
FrameworkElement elemto = mytab.SelectedItem as FrameworkElement;
}
Console.WriteLine("tab is changing.");
}

wpf lable text binding not working, where i am going wrong

I am new to WPF and trying something like this to update a label text in the WPF form from the class.
The onchange event is getting triggered, but not getting displayed on the form
Here is my class
Public Class ExtractDetails
Inherits UserControl
Implements INotifyPropertyChanged
Private _prdFrstName as string
Public Event PropertyChanged As PropertyChangedEventHandler Implements INotifyPropertyChanged.PropertyChanged
Public Property PrdFrstName() As String
Get
Return _prdFrstName
End Get
Set(ByVal value As String)
If _prdFrstName <> value Then
_prdFrstName = value
Me.OnPropertyChanged("PrdFrstName")
End If
End Set
End Property
Public Sub suMainStrt()
PrdFrstName = strComurl ''contyains teh URL to nagigate to
webBrwFrst = New WebBrowser
webBrwFrst.Navigate(New Uri(strComurl))
Call extract(webBrwFrst, strComurl)
end sub
end class
the url keeps on changing as i ma getting the values from an excel file and looping for each URL.
i wanted to display the URL currently working now
this is my XAML
<Window x:Class="Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Avenet Prduct Description Extractor" Height="396.627" Width="588.123" Background="AliceBlue" Icon="LGIcon.ico">
<Grid Height="341.077" Width="567.721" Background="AliceBlue">
<StackPanel Margin="170.225,226.418,3.143,0" Name="StackPanel1" Height="97.994" VerticalAlignment="Top">
<Label Height="30.906" Name="lblCrntSt1" Content="{Binding Path=PrdFrstName, UpdateSourceTrigger=PropertyChanged}" Width="161" BorderThickness="2" BorderBrush="AliceBlue" Background="Red" Foreground="White" FontSize="13"></Label>
</StackPanel>
</Grid>
and this is my windows class.
Class Window1
Dim clsIniti As New ExtractDetails
Public Sub New()
' This call is required by the Windows Form Designer.
InitializeComponent()
'clsIniti = New ExtractDetails
Me.DataContext = clsIniti
End Sub
end class
without updating the text labels the entire function is working good. but i wish to display few things. where i am going wrong
I tried data binding by removing few parts to new created project. it works there. so some thing wrong in this code??? :`(
I see two possible causes that this doesn't work for you.
A. How does your OnPropertyChanged method look like?
' Correct implementation:
Private Sub OnPropertyChanged(propertyName As String)
RaiseEvent PropertyChanged(Me, New PropertyChangedEventArgs(propertyName))
End Sub
B. Make sure the ExtractDetails instance you call suMainStrt on, is the same as your DataContext instance. Test this by calling suMainStrt directly from the constructor of Window1:
Class Window1
Dim clsIniti As New ExtractDetails
Public Sub New()
' This call is required by the Windows Form Designer.
InitializeComponent()
'clsIniti = New ExtractDetails
Me.DataContext = clsIniti
' test (if this works, your problem is B.)
clsIniti.suMainStrt()
End Sub
End Class
As a side note: Unless you have good reasons to do this, I suggest you create a dedicated viewmodel (class, not usercontrol) that contains the properties you want to bind to.

Setting User Control's DataContext from Code-Behind

This should be pretty easy, but it throws VS2008 for a serious loop.
I'm trying out WPF with MVVM, and am a total newbie at it although I've been developing for about 15 years, and have a comp. sci. degree. At the current client, I am required to use VB.Net.
I have renamed my own variables and removed some distractions in the code below, so please forgive me if it's not 100% syntactically perfect! You probably don't really need the code to understand the question, but I'm including it in case it helps.
I have a very simple MainView.xaml file:
<Window x:Class="MyApp.Views.MainView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Main Window" Height="400" Width="800" Name="MainWindow">
<Button Name="Button1">Show Grid</Button>
<StackPanel Name="teststack" Visibility="Hidden"/>
</Window>
I also have a UserControl called DataView that consists of a DataGrid:
<UserControl x:Class="MyApp.Views.DataView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:WpfToolkit="http://schemas.microsoft.com/wpf/2008/toolkit" >
<Grid>
<WpfToolkit:DataGrid
ItemsSource="{Binding Path=Entries}" SelectionMode="Extended">
</WpfToolkit:DataGrid>
</Grid>
</UserControl>
The constructor for the DataView usercontrol sets up the DataContext by binding it to a view model, as shown here:
Partial Public Class DataView
Dim dataViewModel As ViewModels.DataViewModel
Public Sub New()
InitializeComponent()
dataViewModel = New ViewModels.DataViewModel
dataViewModel.LoadDataEntries()
DataContext = dataViewModel
End Sub
End Class
The view model for DataView looks like this (there isn't much in ViewModelBase):
Public Class DataViewModel
Inherits ViewModelBase
Public Sub New()
End Sub
Private _entries As ObservableCollection(Of DataEntryViewModel) = New ObservableCollection(Of DataEntryViewModel)
Public ReadOnly Property Entries() As ObservableCollection(Of DataEntryViewModel)
Get
Return _entries
End Get
End Property
Public Sub LoadDataEntries()
Dim dataEntryList As List(Of DataEntry) = DataEntry.LoadDataEntries()
For Each dataentry As Models.DataEntry In dataEntryList
_entries.Add(New DataEntryViewModel(dataentry))
Next
End Sub
End Class
Now, this UserControl works just fine if I instantiate it in XAML. When I run the code, the grid shows up and populates it just fine.
However, the grid takes a long time to load its data, and I want to create this user control programmatically after the button click rather than declaratively instantiating the grid in XAML. I want to instantiate the user control, and insert it as a child of the StackPanel control:
Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.Windows.RoutedEventArgs) Handles Button1.Click
Dim dataView As New DataView
teststack.Children.Add(dataView)
End Sub
When I do this, as soon as the Button1_Click finishes, my application locks up, starts eating RAM, and hits the CPU about 50%.
Am I not instantiating my UserControl properly? It all seems to come down to the DataContext assignment in DataEntry's constructor. If I comment that out, the app works as expected (without anything in the grid, of course).
If I move this code block into Button1_Click (basically moving DataEntry's constructor code up a level), the app still fails:
dataViewModel = New ViewModels.DataViewModel
dataViewModel.LoadDataEntries()
dataView.DataContext = dataViewModel
I'm stumped. Can anybody give me some tips on what I could be doing wrong, or even how to debug what infinite loop my app is getting itself into?
Many thanks.
The root cause of your issue appears to be either the raw amount of data you're loading or some inefficiency in how you load that data. Having said that, the reason you're seeing the application lock up is that you're locking the UI thread when loading the data.
I believe that in your first case the data loading has been off loaded onto another thread to load the data. In you second example you're instantiating the control on the UI thread and as a result all the constructor and loading logic is performed on the current thread (the UI thread). If you offload this work onto another thread then you should see similar results to the first example.
I eventually gave up on trying to get the DataContext on the UserControl set during instantiation of the UserControl (either in XAML or code). Now I load up the data and set the DataContext of the UserControl in an event in the UserControl (IsVisibleChanged, I believe). When I instantiate the UserControl in XAML, I have it's Visibility set to Hidden. When Button1 is clicked, I set the UserControl's Visibility to Visible. So the UserControl pops into view, and it loads up its data and DataContext is set. Seems to work, but also seems very kludgey. :-( Thanks for the help, folks!
If it's only a matter of your control taking a long time to populate data, you should populate the control on another thread then add it through a delegate:
Since I'm not too good at writing VB.NET, but here's the C# equivalent:
private void Button1_Click(Object sender, RoutedEventArgs e)
{
Thread thr = new Thread(delegate()
{
DataView dataView = new DataView();
this.Dispatcher.BeginInvoke((Action) delegate()
{
teststack.Children.Add(dataView);
});
});
thr.Start();
}

Resources