I have a wpf custom control with (as is common) a shared constructor. This one looks like this;
Shared Sub New()
'This OverrideMetadata call tells the system that this element wants to provide a style that is different than its base class.
'This style is defined in Themes\Generic.xaml
DefaultStyleKeyProperty.OverrideMetadata(GetType(VtlDataNavigator), New FrameworkPropertyMetadata(GetType(VtlDataNavigator)))
ItemsSourceProperty.OverrideMetadata(GetType(VtlDataNavigator), New FrameworkPropertyMetadata(Nothing, AddressOf OnItemsSourceHasChanged))
End Sub
The control inherits ItemsControl and I want to have notification of when the itemsSource changes hence the second line in the constructor. OnItemsSourceHasChanged needs to be a shared sub in order for the line in the constructor to compile. Fine.
In the shared sub I have the following:
Private Shared Function OnItemsSourceHasChanged(d As DependencyObject, e As DependencyPropertyChangedEventArgs) As Object
RecordCount = Items.SourceCollection.Cast(Of Object)().Count()
End Function
which of course fails to compile because you cannot refer to an instance method of a class from within a shared method without creating a specific instance of the class. Even If a create a separate non shared handler for this I will still end up with the same error when trying to call it.
In essence all I want is the total RecordCount from the ItemsSource (hence my need to know when it's changed) so that I can then assign that value to RecordCount. However what I appear to have instead is the proverbial catch 22 of errors.
The answer is probably staring me in the face, but for now it escapes me. Any Ideas?
Related
I've noticed that when a property's backing field has the WithEvents modifier, value assignment can "lag" for lack of better words. I've reproduced the behavior in a simple demo, so the purpose of WithEvents won't be evident here (and thus it won't be constructive to say "just get rid of it")
Public Class ItemViewModel
Public Property Id As Integer
End Class
Public Class ViewModel
Inherits ViewModelBase
Private WithEvents _item As ItemViewModel = New ItemViewModel() With {.Id = 0}
Public Property Item As ItemViewModel
Get
Return _item
End Get
Set(value As ItemViewModel)
SetProperty(_item, value)
End Set
End Property
...
SetProperty definition:
Protected Function SetProperty(Of T)(ByRef field As T, value As T, <CallerMemberName> Optional name As String = Nothing) As Boolean
If (EqualityComparer(Of T).Default.Equals(field, value)) Then
Return False
End If
field = value
NotifyPropertyChanged(name)
Return True
End Function
When I update the Item property to be a new item with an incremented id, the property getter is hit as soon as the event fires, as expected. However, the value of the backing field is still the old value! If I add another PropertyChanged event right after the SetProperty call, the backing field will have the correct value at that point. Of course, if I take out WithEvents, it works as expected with only one event.
This is the only time I've seen SetProperty fail in such a way. What is the problem that WithEvents is causing?
UPDATE: When ViewModel implements INotifyPropertyChanged directly, instead of inheriting from the base, and raises PropertyChanged after setting the value, it works.
What's going on here is that WithEvents is a feature that the .NET Framework itself does not natively support. VB.NET is implementing it on top of .NET. The feature is there because it was also provided by VB6. The way the feature was implemented in VB6, though, is very different because of a fundamental difference in the event models between COM and .NET.
I won't go into how VB6 implemented the feature; that isn't really relevant. What's important is how events work with .NET. Basically, with .NET, events have to be explicitly hooked and unhooked. When events are defined, there are a lot of parallels with how properties are defined. In particular, there is a method that adds a handler to an event and a method that removes a handler, similar to the symmetry between the "set" and "get" methods a property has.
The reason events use methods like this is to hide the list of attached handlers from outside callers. If code outside of a class had access to the full list of attached handlers, it would be possible for it to interfere with it, which would be a very poor programming practice potentially resulting in very confusing behaviour.
VB.NET exposes direct calls to these event "add" and "remove" methods through the AddHandler and RemoveHandler operators. In C#, exactly the same underlying operation is expressed using the += and -= operators, where the left-hand argument is an event member reference.
What WithEvents gives you is syntactic sugar that hides the AddHandler and RemoveHandler calls. What's important to recognize is that the calls are still there, they're just implicit.
So, when you write code like this:
Private WithEvents _obj As ClassWithEvents
Private Sub _obj_GronkulatedEvent() Handles _obj.GronkulatedEvent
...
End Sub
..you are asking VB.NET to ensure that whatever object is assigned to _obj (keeping in mind that you can change that object reference at any time), the event GronkulatedEvent should be handled by that Sub. If you change the reference, then the old object's GronkulatedEvent should be immediately detached, and the new object's GronkulatedEvent attached.
VB.NET implements this by turning your field into a property. Adding WithEvents means that the field _obj (or, in your case, _item) is not actually a field. A secret backing field is created, and then _item becomes a property whose implementation looks like this:
Private __item As ItemViewModel ' Notice this, the actual field, has two underscores
Private Property _item As ItemViewModel
<CompilerGenerated>
Get
Return __item
End Get
<CompilerGenerated, MethodImpl(Synchronized)>
Set(value As ItemViewModel)
Dim previousValue As ItemViewModel = __item
If previousValue IsNot Nothing Then
RemoveHandler previousValue.GronkulatedEvent, AddressOf _item_GronkulatedEvent
End If
__item = value
If value IsNot Nothing Then
AddHandler value.GronkulatedEvent, AddressOf _item_GronkulatedEvent
End If
End Set
End Property
So, why does this cause the "lag" you see? Well, you can't pass a property "ByRef". To pass something "ByRef", you need to know its memory address, but a property hides the memory address behind "get" and "set" methods. In a language like C#, you would simply get a compile-time error: A property is not an L-value, so you cannot pass a reference to it. However, VB.NET is more forgiving and writes extra code behind the scenes to make things work for you.
In your code, you are passing what looks like a field, the _item member, into SetProperty, which takes the parameter ByRef so it can write a new value. But, due to WithEvents, the _item member is really a property. So, what does VB.NET do? It creates a temporary local variable for the call to SetProperty, and then assigns it back to the property after the call:
Public Property Item As ItemViewModel
Get
Return _item ' This is actually a property returning another property -- two levels of properties wrapping the actual underlying field -- but VB.NET hides this from you
End Get
Set
' You wrote: SetProperty(_item, value)
' But the actual code emitted by the compiler is:
Dim temporaryLocal As ItemViewModel = _item ' Read from the property -- a call to its Get method
SetProperty(temporaryLocal, value) ' SetProperty gets the memory address of the local, so when it makes the assignment, it is actually writing to this local variable, not to the underlying property
_item = temporaryLocal ' Once SetProperty returns, this extra "glue" code passes the value back off to the property, calling its Set method
End Set
End Property
So, because WithEvents converted your field to a property, VB.NET had to defer the actual assignment to the property until after the call to SetProperty returns.
Hope that makes sense! :-)
I'm fairly new to WPF and still try to get the feeling on how to do something with built-in functions rather than inventing the wheel on my own again.
Today I stumbled upon a problem, that I couldn't solve with built-in functions and the possible ways I could think of I didn't like very much. So hopefully you can point me in the right direction or even can name a clever way with built-in functions.
So, for the sake of simplicity let's say I'd like to write a ViewModel for the MailMessage class that can be found in the System.Net.Mail namespace.
Imports System.Collections.ObjectModel
Imports System.Net.Mail
Public Class MailMessageViewModel
Private _message As MailMessage
...
End Class
A MailMessage object has (among others) a property To of type MailAddressCollection containing all the recipients for my e-mail as MailAddress objects.
In my ViewModel I wrap this collection of MailAddress objects into an ObservableCollection.
And here's my first question, how do I do that. Do I use:
Public ReadOnly Property Recipients As ObservableCollection(Of MailAddress)
Get
Return New ObservableCollection(Of MailAddress)(_message.To)
End Get
End Property
or do I use:
Private _recipients As ObservableCollection(Of MailAddress)
Public ReadOnly Property Recipients As ObservableCollection(Of MailAddress)
Get
If _recipients Is Nothing Then
_recipients = New ObservableCollection(Of MailAddress)(_message.To)
End If
Return _recipients
End Get
End Property
My view model now has a bindable property Recipients.
Now I'd like to be able to delete an e-mail address from the To collection of my MailMessage.
But when I delete an address from the ObservableCollection, my UI gets updated properly, but the To collection stays untouched. If I delete directly from the To collection of my MailMessage, the ObservableCollection and therefore my UI don't reflect the changes.
Do I really have to wire the ObservableCollection and the corresponding source collection manually by using the CollectionChanged event or by doing all changes twice (in the ObservableCollection and in the real collection)? Or is there any clever WPF way I don't know of?
Don't "wrap" anything.
Simply create a View Model containing properties needed to send your mail message.
At some point in future, you'll actually be sending the message. For example, the user clicks a Send button that fires an ICommand somewhere. At this time, convert your ViewModel into a MailMessage.
You cannot "wrap" one collection within another without lots of code. It only takes a few minutes to copy property values from an instance of one type to an instance of another type.
If the changes always go from the ObservableCollection to the original List, i think that you could add a handler to 'CollectionChanged' event of the ObservableCollection. I think that doing it this way won't be so onerous.
AddHandler Recipients.CollectionChanged, AddressOf RecipientsCollChanged
....
Private Sub RecipientsCollChanged(sender As Object, e As NotifyCollectionChangedEventArgs)
If e.OldItems IsNot Nothing Then
For Each elem In e.OldItems
_message.To.Remove(elem)
Next
End If
End Sub
Obviously, if you want, you can also handle the modify and the adding of elements into the ObservableCollection using the informations into the NotifyCollectionChangedEventArgs parameter.
I have a WPF application with form that has a textbox named "txtStatusWindow". I also have a vb.net class handed to me by a co-worker that needs to be called and executed by the code in my application code-behind. My co-worker insists that I will need to use common .net events to update the textbox on my form.
The separate vb.net class:
Public Class globalclass
Public Event txtStatusWindow(ByVal Text As String)
Public Sub InitializeProgram()
RaiseEvent txtStatusWindow("Updating something.")
System.Threading.Thread.Sleep(2000)
RaiseEvent txtStatusWindow("Updating something else.")
System.Threading.Thread.Sleep(2000)
RaiseEvent txtStatusWindow("Updating something other than else.")
System.Threading.Thread.Sleep(2000)
RaiseEvent txtStatusWindow("Updating something other than the else stuff.")
System.Threading.Thread.Sleep(2000)
End Sub
End Class
I need to be able to call the sub "InitializeProgram()" from my code-behind, and it needs to be able to update "txtStatusWindow.text" as it runs.
I told him that the updating of the text box can be done with data-binding, but I don't know how to integrate a separate class like this into my project, how to call methods in it, or how to cause it to update my text blocks through data binding.
I also suggested that the methods in this class aren't optimal for connecting to the WPF project anyway, but he just wrote it as an example to discover how to connect the two projects.
Eventually, I will need to integrate classes like these that will be running separate threads to update their data from a dynamic source, and cause many controls to update in my application.
So far, the only way we have been able to get this to work from my code-behind is this:
Partial Public Class SplashScreen
Dim NewText as String
Public WithEvents Globals As globalclass = New globalclass
Public Delegate Sub StringDelegate(ByVal Text As String)
Public SplashText As String
Public Sub New()
MyBase.New()
Me.InitializeComponent()
Me.Show()
Globals.InitializeProgram()
End Sub
Public Sub UpdateSplashscreenHandler(ByVal Text As String) Handles Globals.UpdateSplashScreen
StatusWindowText.Text = Text
End Sub
Notwithstanding the fact that the WPF screen "freezes" until the "globalclass InitializeProgram" method completes (txtStatusWindow.Text does not update while sub without using the esoteric "refresh" extension...), I fully believe we are going about this the wrong way.
There are precious few examples out there concerning the integration and then binding to objects in existing code. Thanks for examining our little quandary.
If this status window is in XAML and the status window is a UserControl, then add a StatusText dependency property to the status window. Then, in the XAML you can bind to the value of that property with something like:
<UserControl x:Name="MyStatusWindow" ...>
<TextBlock Text="{Binding Path=StatusText, ElementName=MyStatusWindow}" />
</UserControl>
Then, from your event, just update the value of that StatusText property.
(Is that even close to what you were asking?)
Also, about that freezing: Instead of doing that updating in the constructor of that class, you might want to do it from the Loaded event of that control. It will still be freezing, though, unless you move it to a separate thread. Right now, that's happening on the same thread that the UI message pump is running on. This is the Dispatcher for that UI.
Something like:
<DesignTimeHidden()> _
Private Sub UserControl_IsVisibleChanged(sender As Object, _
e As DependencyPropertyChangedEventArgs) Handles Me.IsVisibleChanged
End Sub
It sounds like you want a method which, if called at design-time, is ignored, but can still be called at run-time.
This is not possible with an attribute. However your method code can check if it is being called at design time and return without doing anything. How you do this depends on your environment.
For components such as WinForms or
ASP.NET controls, check the
DesignMode property (note this is
not set until after construction, so
is not reliable in the constructor or
methods called from the constructor).
For WPF components, call
DesignerProperties.IsInDesignMode(this).
I'd like to enable/disable some other controls based on how many items are in my ListView control. I can't find any event that would do this, either on the ListView itself or on the ListViewItemCollection. Maybe there's a way to generically watch any collection in C# for changes?
I'd be happy with other events too, even ones that sometimes fire when the items don't change, but for example the ControlAdded and Layout events didn't work :(.
#Domenic
Not too sure, Never quite got that far in the thought process.
Another solution might be to extend ListView, and when adding and removing stuff, instead of calling .items.add, and items.remove, you call your other functions. It would still be possible to add and remove without events being raised, but with a little code review to make sure .items.add and .items.remove weren't called directly, it could work out quite well. Here's a little example. I only showed 1 Add function, but there are 6 you would have to implement, if you wanted to have use of all the available add functions. There's also .AddRange, and .Clear that you might want to take a look at.
Public Class MonitoredListView
Inherits ListView
Public Event ItemAdded()
Public Event ItemRemoved()
Public Sub New()
MyBase.New()
End Sub
Public Function AddItem(ByVal Text As String) As ListViewItem
RaiseEvent ItemAdded()
MyBase.Items.Add(Text)
End Function
Public Sub RemoveItem(ByVal Item As ListViewItem)
RaiseEvent ItemRemoved()
MyBase.Items.Remove(Item)
End Sub
End Class
I can't find any events that you could use. Perhaps you could subclass ListViewItemCollection, and raise your own event when something is added, with code similar to this.
Public Class MyListViewItemCollection
Inherits ListView.ListViewItemCollection
Public Event ItemAdded(ByVal Item As ListViewItem)
Sub New(ByVal owner As ListView)
MyBase.New(owner)
End Sub
Public Overrides Function Add(ByVal value As System.Windows.Forms.ListViewItem) As System.Windows.Forms.ListViewItem
Dim Item As ListViewItem
Item = MyBase.Add(value)
RaiseEvent ItemAdded(Item)
Return Item
End Function
End Class
I think the best thing that you can do here is to subclass ListView and provide the events that you want.