Make WPF-control shared - wpf

I'm trying to access Label from another class's method running in background thread with the help of MainWindow Class's Public Shared Sub like this:
Private Delegate Sub ProgressReportInvoker(ByVal progressStr As String)
Public Shared Sub ProgressReport(ByVal progressStr As String)
If MainWindow.Label.Dispatcher.CheckAccess() Then
MainWindow.Label.Content = progressStr
Else
MainWindow.Label.Dispatcher.Invoke(
New ProgressReportInvoker(AddressOf ProgressReport),
progressStr)
End If
End Sub
Call from another class is below:
MainWindow.ProgressReport("Sample text")
But I have this error on "MainWindow.Label":
Reference to a non-shared member requires an object reference.
I noticed that if I declare Label in MainWindow.g.i.vb as Public Shared than error is gone:
#ExternalSource ("..\..\MainWindow.xaml", 11)
<System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1823:AvoidUnusedPrivateFields")>
Public Shared WithEvents Label As System.Windows.Controls.Label
#End ExternalSource
But this file is generated automatically from the *.XAML file so it takes previous look when I compile the code.
Is there any way to make control shared in *.XAML file or may be there are any alternatives of making my task possible?

You should access the instance of the MainWindow and not the type itself:
Public Shared Sub ProgressReport(ByVal progressStr As String)
Dim mainWindow = Application.Current.Windows.OfType(Of MainWindow).FirstOrDefault()
If mainWindow.Label.Dispatcher.CheckAccess() Then
mainWindow.Label.Content = progressStr
Else
mainWindow.Label.Dispatcher.Invoke(
New ProgressReportInvoker(AddressOf ProgressReport),
progressStr)
End If
End Sub
I tried this before but problem is in multitasking. I can't access the form from another thread without some special moves which I don't know about
You can only access a UI control in the thread on which it was originally created:
Application.Current.Dispatcher.BeginInvoke(New Action(Sub()
Dim mainWindow = Application.Current.Windows.OfType(Of MainWindow).FirstOrDefault()
mainWindow.Label.Content = progressStr
End Sub))

It is very bad practice to use anything global (shared/static). Use instance of class or other mechanism (Dependency Injection, messaging, events, etc.) for communication between independent classes.

Related

SerializationException when raising event in other instance via IPC

I'm trying to send some information from a newly launched instance of my application to the currently running instance (namely one argument in the form of a string) by way of ipc.
Class RemoteObject
Inherits MarshalByRefObject
Public Event ParamEvent As RemoteObject.ParamEventHandler
Public Property path As String = ""
Delegate Sub ParamEventHandler()
Public Sub FireEvent()
RaiseEvent ParamEvent()
End Sub
End Class
Friend WithEvents on MainWindow:
Friend WithEvents theRemoteObject As RemoteObject
I'm setting it up in my first instance like this.
theRemoteObject = New RemoteObject
theRemoteObject.path = "blah"
theChannel = New IpcChannel("localhost:9090")
ChannelServices.RegisterChannel(theChannel, False)
RemotingServices.Marshal(theRemoteObject, "ParamReceiver")
And in my second instance:
Dim uri As String = "ipc://localhost:9090/ParamReceiver"
theChannel = New IpcChannel
ChannelServices.RegisterChannel(theChannel, False)
theRemoteObject = DirectCast(RemotingServices.Connect(GetType(RemoteObject), uri), RemoteObject)
theRemoteObject.path = "blarg"
theRemoteObject.FireEvent()
Everything works properly; when the second instance starts the path property changes from "blah" to "blarg" in both instances. However, when I add this event handler in MainWindow:
Public Sub ParamHandler() Handles theRemoteObject.ParamEvent
'do stuff here
End Sub
It halts on this line in the second instance:
theRemoteObject = DirectCast(RemotingServices.Connect(GetType(RemoteObject), uri), RemoteObject)
With the following exception:
An exception of type 'System.Runtime.Serialization.SerializationException' occurred in mscorlib.dll but was not handled in user code
Additional information: Type 'Cutlist3.MainWindow' in Assembly 'Cutlist3, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null' is not marked as serializable.
I don't know what this means or where to start in debugging it. Any information you could provide would be super helpful and appreciated!
For future reference, I solved my problem by creating a new object declaration in the second instance Dim SecondObject as RemoteObject = DirectCast ... instead of using the original Friend WithEvents theRemoteObject declaration.

How to use another ioc container with mef?

i am using mef with prism. i can use mef because i like the export, import, metadata attributes and mostly aggregate cagalog usage. so i want to use mef with prism project.
In my plan, my solution projects must be use autofac or castle windsor ioc container and i implement like that except prism project(wpf). In case, i am not prefer to use autofac or castle windsor instead of mef's default di/ioc but too many alternative usage of personal experimantals are failed.
Is there a any stable sample project i can use? I want to change only ioc of mef with all mef functionalty.
My classic mef bootstrapper code is bellow
Imports System.ComponentModel.Composition.Hosting
Imports Microsoft.Practices.Prism.MefExtensions
Imports Microsoft.Practices.ServiceLocation
Public Class Bootstrapper2
Inherits MefBootstrapper
Protected Overrides Sub ConfigureContainer()
MyBase.ConfigureContainer()
Dim ag As New AggregateCatalog()
ag.Catalogs.Add(New AssemblyCatalog(GetType(Bootstrapper2).Assembly))
ag.Catalogs.Add(New DirectoryCatalog("..\..\modules\", "Prism.Sample.Modules.*.dll"))
Me.AggregateCatalog.Catalogs.Add(ag)
End Sub
Protected Overrides Function CreateShell() As DependencyObject
Dim s As Shell = ServiceLocator.Current.GetInstance(Of Shell)()
Return s
End Function
Protected Overrides Sub InitializeShell()
Application.Current.MainWindow = Shell()
Application.Current.MainWindow.Show()
End Sub
End Class
Shell's code is bellow:
Imports System.ComponentModel.Composition
<Export()> _
Public Class Shell
Sub New()
InitializeComponent()
End Sub
<Import(AllowRecomposition:=False)> _
Public Property ViewModel() As ShellViewModel
Get
Return CType(Me.DataContext, ShellViewModel)
End Get
Set(value As ShellViewModel)
Me.DataContext = value
End Set
End Property
End Class
Now, everythings working like an expected.
modified/overrided bootstrapper's ConfigureServiceLocator() method is bellow.
Private autofacBuilder As New Autofac.ContainerBuilder
Protected Overrides Sub ConfigureServiceLocator()
Dim autofacContainer = autofacBuilder.Build()
Dim autofacSL = New Prism.AutofacExtension.AutofacServiceLocatorAdapter(autofacContainer)
ServiceLocator.SetLocatorProvider(Function() autofacSL)
End Sub
then i have got an too many resolving exception for example:
exception message:
Activation error occured while trying to get instance of type RegionAdapterMappings, key "".
Prism or another code base trying to resolve IRegionAdapterMappings from the servicelocator but currentservice locator not knowns what is this.Because mef is allready registered this types((ConfigureContainer) before CreateServiceLocator.
So, then i trying to add mef's aggregate catalog to register autofac container with Autofac.Integration.Mef project like this:
Private autofacBuilder As New Autofac.ContainerBuilder
Protected Overrides Sub ConfigureServiceLocator()
autofacBuilder.RegisterComposablePartCatalog(Me.AggregateCatalog)
Dim autofacContainer = autofacBuilder.Build()
Dim autofacSL = New Prism.AutofacExtension.AutofacServiceLocatorAdapter(autofacContainer)
ServiceLocator.SetLocatorProvider(Function() autofacSL)
End Sub
Then i have got a diffrent exception: IServiceLocator not registered etc...
I have not a complately solutions for changing mef's ioc container because its own container types and uses her own extensibility. Tried to use Autofac.Integration.Mef but maybe it not future compatible. maybe not developep when mef' new releases.
I am a big blakc hole i think. Is there a any way can't i see?
Thanks.

DispatcherObject cast woes and Async / ObservableCollection issues in WPF

The code below pulls out a bunch of records from an Access 2010 database; hence rolling my own connector bits. I've succeeded in doing the observablecollection and made it all bind up with nice drag and drop data sources, from my own objects. However, like a daft person, I want to do this Asynchronously. Yet, I've got a small cast monster problem, and I don't know what to feed it! Can anyone advise me - I've tried a lot of reading around, but the concepts are just a little too many at once on a Friday afternoon and I'm struggling to make any real headway.
The line I'm having trouble with is:
Dim dispatcherObject As DispatcherObject = CType (handler.Target, DispatcherObject )
The exception is:
Unable to cast object of type '_Closure$__2[SomeRecord_Viewer.SomeRecord]' to type 'System.Windows.Threading.DispatcherObject'.
I've managed to make a WPF listbox populate via the code below, however only by commenting out a part of the ObservableCollectionEx class. This causes synchronisation problems and a crash after a few hundred records are entered.
Class that builds the threaded list of entities - in this case, an ObservableCollectionEx(Of SomeRecord):
Class SomeRecordSet
Inherits ObservableCollectionEx( Of SomeRecord)
Private Shared Property _SomeRecordList As New ObservableCollectionEx(Of SomeRecord )
Public Shared ReadOnly Property SomeRecordList As ObservableCollectionEx(Of SomeRecord )
Get
If _SomeRecordList.Count = 0 Then BuildSomeRecordListAsync()
Return _SomeRecordList
End Get
End Property
Public Shared ReadOnly Property ReturnSingleSomeRecord(id As Integer) As SomeRecord
Get
Return ( From SomeRecord In _SomeRecordList Where SomeRecord.id = id Select SomeRecord).First()
End Get
End Property
Private Shared Async Sub BuildSomeRecordListAsync()
Await Task.Run( Sub() BuildSomeRecordList())
Return
End Sub
Private Shared Sub BuildSomeRecordList()
Db.newcmd( "Select * from RecordList ")
While Db.read
Dim SomeRecord As New SomeRecord
With SomeRecord
.id = Db.dbint( "ID")
.type = Db.dbin( "type")
End With
_SomeRecordList.Add(SomeRecord)
End While
End Sub`
Partial code for the SomeRecord class:
Class SomeRecord
Implements INotifyPropertyChanged
Public Event PropertyChanged As PropertyChangedEventHandler Implements INotifyPropertyChanged.PropertyChanged
Private Sub NotifyPropertyChanged( ByVal info As String)
RaiseEvent PropertyChanged(Me , New PropertyChangedEventArgs (info))
End Sub
...'lots of simple properties.
End Class
The threaded collection class code - translated from another online source.
'I use PostSharp for try catch stuff.
`
Public Class ObservableCollectionEx (Of T )
Inherits ObservableCollection( Of T)
' Override the event so this class can access it
Public Shadows Event CollectionChanged As System.Collections.Specialized.NotifyCollectionChangedEventHandler
Protected Overrides Sub OnCollectionChanged( ByVal e As System.Collections.Specialized.NotifyCollectionChangedEventArgs )
Using BlockReentrancy()
Dim eventHandler As System.Collections.Specialized.NotifyCollectionChangedEventHandler = Sub () RaiseEvent CollectionChanged(Me , e)
If (eventHandler Is Nothing) Then Return
Dim delegates() As [Delegate] = eventHandler.GetInvocationList
*******If I comment this out I can populate the Listbox via a CollectionView, however it dies with issues to do with the list not staying synchronised :).
'Walk thru invocation list
For Each handler As System.Collections.Specialized.NotifyCollectionChangedEventHandler In delegates
Dim dispatcherObject As DispatcherObject = CType (handler.Target, DispatcherObject)
' If the subscriber is a DispatcherObject and different thread
If (( Not (dispatcherObject) Is Nothing) AndAlso (dispatcherObject.CheckAccess = False )) Then
' Invoke handler in the target dispatcher's thread
dispatcherObject.Dispatcher.Invoke(DispatcherPriority .DataBind, handler, Me, e)
Else
handler( Me, e)
End If
Next
*******End of stuff I comment out to get working partially***
End Using
End Sub
End Class
From what I can see, you have two problems.
You're assigning the local variable eventHandler to an anonymous method, rather than the actual event handler. It should be:
Dim eventHandler As NotifyCollectionChangedEventHandler = CollectionChangedEvent
NB: You need to use CollectionChangedEvent in VB, not CollectionChanged.
You're using CType to cast the target to a DispatcherObject, which won't work if the target isn't a DispatcherObject. Use TryCast instead:
Dim dispatcherObject As DispatcherObject = TryCast(handler.Target, DispatcherObject)
You can also tidy up the test on the next line by using IsNot:
If dispatcherObject IsNot Nothing AndAlso Not dispatcherObject.CheckAccess Then
WARNING - The code below acts differently to the C# version. The key difference seems to be that in VB you can't Override an Event (Why on earth not?) yet in C# you can.
The result is the Handler is Nothing in VB but not in C# :(.
So the syntax builds without error but the VB version doesn't ever do anything.
Redone with the updated answer in VB. Thank you!
Note I cannot make this work with Entity Framework, yet. But I think that a me and EF issue, not the collection.
The code itself is here for anyone interested. My list DOES populate perfectly fine now. However, I would take this answer of mine with a small pinch of salt until I update saying how I've extensively tested perhaps :)
However the omens are good - here is the original C# author's site: Original Site
Public Class ObservableCollectionEx(Of T)
Inherits ObservableCollection(Of T)
'Override the event so this class can access it
Public Shadows Event CollectionChanged As NotifyCollectionChangedEventHandler
Protected Overrides Sub OnCollectionChanged(ByVal e As NotifyCollectionChangedEventArgs)
Using BlockReentrancy()
Dim eventHandler As System.Collections.Specialized.NotifyCollectionChangedEventHandler = CollectionChangedEvent
If eventHandler Is Nothing Then
Return
End If
Dim delegates() As [Delegate] = CollectionChangedEvent.GetInvocationList
'Walk thru invocation list
For Each handler As NotifyCollectionChangedEventHandler In delegates
Dim dispatcherObject As DispatcherObject = TryCast(handler.Target, DispatcherObject)
' If the subscriber is a DispatcherObject and different thread
If dispatcherObject IsNot Nothing AndAlso Not dispatcherObject.CheckAccess Then
' Invoke handler in the target dispatcher's thread
dispatcherObject.Dispatcher.Invoke(DispatcherPriority.DataBind, handler, Me, e)
Else
handler(Me, e)
End If
Next
End Using
End Sub
End Class

vb.net - accessing an initiated class from another class

I have the following defined in my "Form1":
Dim Database As New DatabaseManager
So that I can access all my database functions anywhere in the main form (database connection is initialized via the "private sub new()" function in the DatabaseManager class.
The works fine for all queries done in form1, however I have many other classes I'd like to be able to access the database from.
Does anyone know how to access the initiated class in my example? Since right now it is initiated in the main form, if I try to access any functions inside any other class functions it does nothing (doesn't error out either).
I'm trying to figoure out how to dim a class one and I can access it from within any class and I can't figure it out.
Thanks!
I would encourage to use dependency injection if you want this.
In essence it would come down to this.
Private _DatabaseManager as DatabaseManager
Public Sub New(Byval DatabaseManager as DatabaseManager)
InitializeComponent()
_DatabaseManager = DatabaseManager
End Sub
TIf you do it like this you can give all your forms the same DatabaseManager or a different one like you please.
There is of course a lot more to it than that . But for that you will have to dig into Dependecy Injection and Inversion of control (DI/IoC)
One thing you could do is create a factory for the DatabaseManager and then just have all your other code call it from that factory. I'm very out of practice with VB syntax, but in C# it might look something like this:
public class DatabaseManagerFactory
{
private static DatabaseManager _current = null;
public static DatabaseManager Current
{
get
{
if (_current == null) _current = new DatabaseManager();
return _current;
}
}
}
VB
Public Class DatabaseManagerFactory
Private Shared _current As DatabaseManager = Nothing
Public Shared ReadOnly Property Current As DatabaseManager
Get
If _current Is Nothing null Then _current = New DatabaseManager()
Return _current
End Get
End Property
End Class
The idea then is that anything in your application which needs to use a DatabaseManager would just call DatabaseManagerFactory.Current to get the one shared instance.
Note that in this case DatabaseManager isn't really a singleton, you can still instantiate one elsewhere in the application if you need to for some reason. If it should be an actual singleton then you'd want to make some modifications to the DatabaseManager class itself. Maybe give it a private constructor and implement this factory directly on the class? Something like this:
public class DatabaseManager
{
private static DatabaseManager _current = null;
public static DatabaseManager Current
{
get
{
if (_current == null) _current = new DatabaseManager();
return _current;
}
}
private DatabaseManager
{
// your initialization of the class
}
}
VB
Public Class DatabaseManager
Private Shared _current DatabaseManager = Nothing
Public Shared ReadOnly Property Current As DatabaseManager
Get
If _current Is Nothing Then _current = New DatabaseManager()
Return _current
End Get
End Property
Private Sub New()
' your initialization of the class
End Sub
End Class
(I encourage anybody more familiar with VB syntax to edit this answer accordingly to better address the question.)
create and initiate databaseManager in your Form1, but declare it Friend instead of Dim. That way you can write a reference to it like : Form1.databaseManager

WPF Custom Control and exposing properties thru DependencyProperty

Ok - I'm pulling my hair out over what I thought was a simple scenario: create a custom Label for bilingual use that contained two additional properties (EnglishText, FrenchText). Currently its structured like this:
Public Class myCustomLabel
Inherits System.Windows.Controls.Label
Public myEnglishTextProperty As DependencyProperty = DependencyProperty.Register("myEnglishText", GetType(String), GetType(myCustomLabel), New PropertyMetadata("English", New PropertyChangedCallback(AddressOf TextChanged)))
Public myFrenchTextProperty As DependencyProperty = DependencyProperty.Register("myFrenchText", GetType(String), GetType(myCustomLabel), New PropertyMetadata("Francais", New PropertyChangedCallback(AddressOf TextChanged)))
Public 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(myCustomLabel), New FrameworkPropertyMetadata(GetType(myCustomLabel)))
End Sub
Public Property myEnglishText() As String
Get
Return MyBase.GetValue(myFrenchTextProperty)
End Get
Set(ByVal value As String)
MyBase.SetValue(myFrenchTextProperty, value)
End Set
End Property
Public Property myFrenchText() As String
Get
Return MyBase.GetValue(myFrenchTextProperty)
End Get
Set(ByVal value As String)
MyBase.SetValue(myFrenchTextProperty, value)
End Set
End Property
Private Sub TextChanged(ByVal d As DependencyObject, ByVal e As DependencyPropertyChangedEventArgs)
If DesignerProperties.GetIsInDesignMode(Me) = True Then
Me.Content = myEnglishText
Else
If myUser.Language = "E" Then
Me.Content = myEnglishText
Else
Me.Content = myFrenchText
End If
End If
End Sub
End Class
My test window grid xaml is simple:
<Grid>
<my:myCustomLabel myEnglishText="English Text" myFrenchText="English Text" Height="25" Width="100" Background="Aqua" Foreground="Black"/>
</Grid>
This seems to work in the development environment - changing the English and French texts change the in the design preview and it works when the app runs and the test window is opened. But only the first time - if I open the test window a second time I receive the following message:
'myEnglishText' property was already
registered by 'myCustomLabel'.
I understand now that if I change the dependency property declarations to shared then this problem goes away - but that leads to a host of other problems like the callback function being required to be shared as well - and thus unable to update the Content (which needs to be instantiated with the class). All I really want is the content property to be updated in design time when the english and french labels are changed.
Is there a way around this? Or maybe are dependency properties overkill for what I need?
You are registering your dependency properties as instance variables, and during the instance constructor. So they are getting registered again every time you instantiate the control, which causes an error the second time. As you have found out, dependency properties need to be static (Shared) members:
Public Shared myEnglishTextProperty As DependencyProperty =
DependencyProperty.Register("myEnglishText", GetType(String), GetType(myCustomLabel),
New PropertyMetadata("English", New PropertyChangedCallback(AddressOf TextChanged)))
You probably need to call OverrideMetadata in your shared constructor (type initialiser) rather than your instance constructor as well.
Regarding your issue with the callback needing to be shared: yes, it will be, but one of the arguments to the callback is the label instance. So you can just cast that to label and call an instance method on that:
private static void TextChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
((MyLabel)d).TextChanged();
}
private void TextChanged()
{
// your code here
}
(forgive C# syntax)
Is the reason you don't want the callback method to be shared because you're accessing the "me" instance? If that's all it is, make it shared and use the "d" parameter. I don't know VB well enough to show you the code, but just create a variable of type myCustomLabel and assign "d" to it (with a cast). Then use that variable (say "lbl") instead:
If DesignerProperties.GetIsInDesignMode(lbl) = True Then
lbl.Content = myEnglishText
Else
If myUser.Language = "E" Then
lbl.Content = myEnglishText
Else
lbl.Content = myFrenchText
End If
End If
Also, there's a slight bug in your example code. Try using this:
Public Property myEnglishText() As String
Get
Return MyBase.GetValue(myEnglishTextProperty)
End Get
Set(ByVal value As String)
MyBase.SetValue(myEnglishTextProperty, value)
End Set
End Property
Instead of this:
Public Property myEnglishText() As String
Get
Return MyBase.GetValue(myFrenchTextProperty)
End Get
Set(ByVal value As String)
MyBase.SetValue(myFrenchTextProperty, value)
End Set
End Property

Resources