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.
Related
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.)
I am following a tutorial on WPF data binding. I am trying to bind to a .NET object's property to a XAML control but the control does not display the expected data. These are what I believe to be the relevant sections of code:
In procedural code: (Note: removed ObservableCollection in PhotoGallery after original post)
Namespace PhotoGallery
Partial Public Class MainWindow
Inherits Window
Private photos As New Photos
...
End Class
Namespace PhotoGallery
Public Class Photos
Inherits Collection(Of Photo)
...
End Class
In XAML (Solution/Project name is Ch13-PhotoGallery):
<Window x:Class="PhotoGallery.MainWindow"
...
xmlns:local="clr-namespace:Ch13_PhotoGallery.PhotoGallery"
...>
<Window.Resources>
<local:Photos x:Key="Photos"/>
</Window.Resources>
And this is the control that is not displaying the data, which is the size of the Photos collection:
<Label x:Name="numItemsLabel" Background="AliceBlue" FontSize="8" Content="{Binding Source={StaticResource Photos}, Path=Count}"/>
When I typed in the < Label >, Intellisense popped up 'Count' for the Path property, so I think that tells me I have everything defined correctly.
If I add this line of procedural code behind to the refresh() method:
numItemsLabel.Content = photos.Count
Then the count is displayed correctly.
But I'm not getting the binding in XAML to display Photos.Count.
This creates a new instance of the Photos class:
<local:Photos x:Key="Photos"/>
If you want to bind to the Photos collection that you have created in your MainWindow.xaml.vb file you should expose it as a public property - you can only bind to properties but not fields - and set the DataContext of the window to an instance of the class where this property is defined, i.e. the window class itself in your case:
Class MainWindow
Public Property Photos As Photos
Public Sub New()
' This call is required by the designer.
InitializeComponent()
DataContext = Me
...
End Sub
End Class
You can the bind directly to the property:
<Label x:Name="numItemsLabel" Background="AliceBlue" FontSize="8" Content="{Binding Path=Photos.Count}"/>
Your ViewModel needs to implement the INotifyPropertyChanged interface, this will let your window listen to changes to your ViewModel
Here's an example of how to implement this interface in VB
https://social.msdn.microsoft.com/Forums/vstudio/en-US/7de44362-8b88-4292-b4ee-0385c3b34d7d/im-just-looking-for-a-simple-vb-net-mvvm-sample-wpf?forum=wpf
ViewModel
Public Class ViewModel
Implements INotifyPropertyChanged
Public Sub New()
Me.myTextValue = "default value..."
End Sub
Private myTextValue As String = String.Empty
Public Property MyTextProperty() As String
Get
Return Me.myTextValue
End Get
Set(ByVal value As String)
Me.myTextValue = value
NotifyPropertyChanged("MyTextProperty")
End Set
End Property
Public Event PropertyChanged As PropertyChangedEventHandler _
Implements INotifyPropertyChanged.PropertyChanged
Private Sub NotifyPropertyChanged(ByVal propertyName As String)
RaiseEvent PropertyChanged(Me, New PropertyChangedEventArgs(propertyName))
End Sub
End Class
XAML
<Window x:Class="MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525">
<Grid>
<TextBox Text="{Binding MyTextProperty}"/>
</Grid>
</Window>
XAML Code Behind
Class MainWindow
Sub New()
' This call is required by the designer.
InitializeComponent()
' Add any initialization after the InitializeComponent() call.
Me.DataContext = New ViewModel()
End Sub
End Class
So I have a Grid with some checkboxes, etc inside it and wanted to set them all to readonly , I added the IsEnabled part below:
<Grid IsEnabled="{Binding IsFieldReadOny}">
And in the code behind added this:
Private _isFieldReadOnly As Boolean = True
Public Property IsFieldReadOny() As Boolean
Get
Return _isFieldReadOnly
End Get
Set(value As Boolean)
_isFieldReadOnly = value
End Set
End Property
But when I put breakpoint, it does not get hit or do anything.
If I manually hard code a True for the grid, then it works.
I am new to both WPF and VB syntax, so it might be something easy that I am not doing right.
Here is a very simple example of MVVM and binding with one way out of TONS to do things. Binding in and of itself has many many options of traversing a visual tree with 'RelativeSource' and scoping. As well as mode options and other settings. I chose to focus on keeping it simple though. I just want a view that has a textbox, you can change yourself, a button you can hit, a label that will update from the text you changed.
So here is a basic view:
<Window x:Class="MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:SimpleWPF"
mc:Ignorable="d"
Title="MainWindow" Height="350" Width="525">
<StackPanel>
<TextBox Text="{Binding Text}" Height="30" />
<Button Content="Example" Command="{Binding DoCommand}" />
<Label Content="{Binding Output}" Height="30" />
</StackPanel>
</Window>
I want to set up a single helper class for a 'DelegateCommand'. You can do this many ways but essentially I am saving repeat methods for later reuse for commands to help with an ICommand interface.
Public Class DelegateCommand(Of T)
Implements ICommand
Private _execute As Action(Of T)
Public Sub New(execute As Action(Of T))
_execute = execute
End Sub
Public Event CanExecuteChanged As EventHandler
Private Event ICommand_CanExecuteChanged As EventHandler Implements ICommand.CanExecuteChanged
Private Function ICommand_CanExecute(parameter As Object) As Boolean Implements ICommand.CanExecute
Return True
End Function
Private Sub ICommand_Execute(parameter As Object) Implements ICommand.Execute
_execute.Invoke(DirectCast(parameter, T))
End Sub
End Class
Now in my Code behind of the view it should be pretty minimal except this:
Class MainWindow
Public Sub New()
' This call is required by the designer.
InitializeComponent()
' Add any initialization after the InitializeComponent() call.
Me.DataContext = New MainViewModel()
End Sub
End Class
And my MainViewModel is pretty simple in this case is pretty simple and I am only implementing INotifyPropertyChanged. I would usually do most of my stuff like this in a base class and inherit that on larger solutions.
Imports System.ComponentModel
Public Class MainViewModel
Implements INotifyPropertyChanged
Private _text As String
Public Property Text As String
Get
Return _text
End Get
Set(ByVal value As String)
_text = value
OnPropertyChanged(NameOf(Text))
End Set
End Property
Private _output As String
Public Property Output As String
Get
Return _output
End Get
Set(ByVal value As String)
_output = value
OnPropertyChanged(NameOf(Output))
End Set
End Property
Public Sub New()
_text = "Test"
End Sub
Public ReadOnly Property DoCommand As New DelegateCommand(Of Object)(AddressOf DoIt)
Private Sub DoIt(obj As Object)
Output = $"{Text} {DateTime.Now.ToLongDateString}"
End Sub
#Region "Implement INotifyProperty Changed"
Public Event PropertyChanged As PropertyChangedEventHandler Implements INotifyPropertyChanged.PropertyChanged
Public Sub OnPropertyChanged(ByVal info As String)
RaiseEvent PropertyChanged(Me, New PropertyChangedEventArgs(info))
End Sub
#End Region
End Class
When you use a generic binding you are looking at the DataContext of the object and by generic I mean a {Binding PropertyName} without any other parameters. In order to bind to a property within your code behind (which I don't recommend doing) then you need to tell the binding to look at that location. You also need to use a dependency property for binding on UIElements since it's already built in.
So to make this work I've named the Window the Grid is in 'window'. Then I've given the binding expression a direct connection to the property.
<Grid IsEnabled="{Binding IsReadOnlyField, ElementName=window}" />
I've then added a binding to the Checkbox as well to the same thing.
<CheckBox Content="Is Grid Enabled" IsChecked="{Binding IsReadOnlyField, ElementName=window}" />
Then in the code behind I've changed the property to a DependencyProperty.
public bool IsReadOnlyField
{
get { return (bool)GetValue(IsReadOnlyFieldProperty); }
set { SetValue(IsReadOnlyFieldProperty, value); }
}
public static readonly DependencyProperty IsReadOnlyFieldProperty =
DependencyProperty.Register(nameof(IsReadOnlyField), typeof(bool), typeof(MainWindow));
This will get the binding working.
If you are not using the code behind and are binding to a ViewModel or any class you should preferably make that class interface INotifyPropertyChanged (although you can also make that ViewModel inherit from DependencyObject and use the same DependencyPropery... It's just normally used for UI elements). Then write the property as normal and in the setter call the property changed event. However, you will most likely set the binding back to the way you had it and just put that ViewModel as the DataContext.
There's A LOT to explain about binding as it can be very flexible and used many different ways. Once you get it though you got it and learning more ways to bind will be simple. I suggest learning exactly how the binding takes place so that you can manipulate and choose the best binding for any situation.
In the code below I am unable to get the WPF Window to be updated from a ansynchronous call. All that is shown is the line "First item". The problem is that the Windowis not launched from a WPF Application, but from a class module. As our main UI application is a VB6 application it launches som WPF elements though calls to a Com visible Net dll. This works very well, but not in the case below.
THE MAIN CONSOLE APPLICATION
Imports Window
Imports System.Windows
Imports System.Threading
Module Module1
Sub Main()
Dim launcher As New WindowLauncher
launcher.LaunchWindow()
Console.WriteLine("Press a key to continue...")
Console.ReadLine()
End Sub
End Module
THE WPF WINDOW LAUNCHER
Imports Window
Public Class WindowLauncher
Public Sub LaunchWindow()
Dim model As New ViewModel
Dim window As New DisplayWindow
model.Dispatcher = window.Dispatcher
window.DataContext = model
window.Show()
model.Collection.Add("Second item.")
model.StartAddingItems()
'window.Close()
End Sub
End Class
THE WPF WINDOW AND ITS VIEWMODEL IN WPF USERCONTROL LIBRARY PROJECT
<Window x:Class="DisplayWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="DisplayWindow" Height="300" Width="300">
<Grid>
<ListBox ItemsSource="{Binding Collection}">
<ListBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding}"/>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</Grid>
</Window>
Imports System.Collections.ObjectModel
Imports System.Threading
Imports System.Windows.Threading
Public Class ViewModel
Public Sub New()
Collection = New ObservableCollection(Of String)
Collection.Add("First item")
End Sub
Public Property Collection As ObservableCollection(Of String)
Public Property Dispatcher As Dispatcher
Public Sub StartAddingItems()
Dim progress As New Progress(Of String)
AddHandler progress.ProgressChanged, AddressOf ProgressChanged
For i = 1 To 10
DoSomething(progress).Wait()
Next
End Sub
Private _counter As Integer
Private Async Function DoSomething(progress As IProgress(Of String)) As Task
Await Task.Delay(10 * _counter)
_counter += 1
progress.Report(CStr(_counter))
End Function
Private Sub ProgressChanged(sender As Object, e As Object)
Dim text = CStr(e)
Me.Dispatcher.BeginInvoke(Sub() AddToCollection(text), DispatcherPriority.Background)
'The challenge seems to be to find the correct Dispatcher. Now the property is set from the WPF Window, but the code never reaches the AddToCollection method. `
AddToCollection(text)
`This call throws the well known "This type of CollectionView does not support changes..." exception.`
End Sub
Private Sub AddToCollection(text As String)
Collection.Add(String.Format("item {0}", text))
End Sub
End Class
The problem lies in the following calls:
model.Collection.Add("Second item.")
model.StartAddingItems()
This seems to block updates to the user interface.
I moved these calls to the window.Loaded event of the WPF.Form (the form holds a reference to the viewmodel) and I set the viewmodel's Dispatcher to that of the window there.
If you're displaying WPF windows, you really need a separate WPF thread (with an STA main loop):
var thread = new Thread(() =>
{
var application = new Application();
application.Run();
});
thread.SetApartmentState(ApartmentState.STA);
thread.Start();
I want my WPF ComboBox's ItemsSource property to be bound to MyListObject's MyList property. The problem is that when I update the MyList property in code, the WPF ComboBox is not reflecting the update. I am raising the PropertyChanged event after I perform the update, and I thought WPF was supposed to automatically respond by updating the UI. Am I missing something?
Here's the CLR object:
Imports System.ComponentModel
Public Class MyListObject
Implements INotifyPropertyChanged
Private _mylist As New List(Of String)
Public Sub New()
_mylist.Add("Joe")
_mylist.Add("Steve")
End Sub
Public Property MyList() As List(Of String)
Get
Return _mylist
End Get
Set(ByVal value As List(Of String))
_mylist = value
End Set
End Property
Public Sub AddName(ByVal name As String)
_mylist.Add(name)
NotifyPropertyChanged("MyList")
End Sub
Private Sub NotifyPropertyChanged(ByVal info As String)
RaiseEvent PropertyChanged(Me, New PropertyChangedEventArgs(info))
End Sub
Public Event PropertyChanged(ByVal sender As Object, _
ByVal e As System.ComponentModel.PropertyChangedEventArgs) _
Implements System.ComponentModel.INotifyPropertyChanged.PropertyChanged
End Class
Here is the XAML:
<Window x:Class="Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Window1" Height="300" Width="300"
xmlns:local="clr-namespace:WpfApplication1"
>
<Window.Resources>
<ObjectDataProvider x:Key="MyListObject" ObjectType="{x:Type local:MyListObject}"/>
</Window.Resources>
<Grid>
<ComboBox Height="23"
Margin="24,91,53,0"
Name="ComboBox1"
VerticalAlignment="Top"
ItemsSource="{Binding Path=MyList, Source={StaticResource MyListObject}}"
/>
<TextBox Height="23"
Margin="24,43,134,0"
Name="TextBox1"
VerticalAlignment="Top" />
<Button Height="23"
HorizontalAlignment="Right"
Margin="0,43,53,0"
Name="btn_AddName"
VerticalAlignment="Top"
Width="75">Add</Button>
</Grid>
</Window>
And here's the simple code-behind:
Class Window1
Private obj As New MyListObject
Private Sub btn_AddName_Click(ByVal sender As System.Object, ByVal e As System.Windows.RoutedEventArgs) _
Handles btn_AddName.Click
obj.AddName(TextBox1.Text)
End Sub
End Class
Thanks!
You are binding to a list of strings. That list class does not implement Inotifyproperty. You should use an observablecollection instead.
I also notice in your code behind you declare
Private obj As New MyListObject
This is not the static resource you bound the combo box to. So your add call would not be reflected in your view.
The ObservableCollection is most likely the solution, but if it still gives you grief, you can directly access your static resource by calling the following code after your list gets updated:
DirectCast(Me.FindResource("MyListObject"), ObjectDataProvider).Source = _myList
Try using a BindingList(Of T) instead of a List(Of T).
Edit: I am new to WPF and it does look like BindingList isn't a complete solution to your problem, but it might be a step in the right direction. I was able to test the MyListObject converted to BindingList in WinForm and the ListChanged event was raised to the ComboBox which then updated its list.
I found this (possible) solution to wrap your class in an ObservableCollection that might help you solve your problem
Enabling WPF Magic Using WCF - Part 1
This is the code to update your object to a BindingList. Combine your code with the code from that resource and you should be good to go.
Public Class MyListObject
...
'Private _mylist As New List(Of String)
Private _mylist As New BindingList(Of String)
...
'Public Property MyList() As List(Of String)
' Get
' Return _mylist
' End Get
' Set(ByVal value As List(Of String))
' _mylist = value
' End Set
'End Property
Public Property MyList() As BindingList(Of String)
Get
Return _mylist
End Get
Set(ByVal value As BindingList(Of String))
_mylist = value
End Set
End Property
...
End Class