I'm new to MEF and started a project to test it. What I'm trying to do is opening a MainForm that would load plugins based on an Interface. Those plugins need to be able to exchange information between them and the MainForm should be able to communicate with all of them too. So I started by creating my MainForm that loads a plugin. The plugin is only a form containing a ListBox. On the MainForm I have a button. I want that button to send a List(of String) to the plugin and that plugin to load that List(of String) in the ListBox. Currently, when I click on the MainForm button, it sends the list to the plugin. But the list is not loading in the plugin ListBox. Looking to find the problem, I added a new button on the MainForm to verify that the plugin property actually contains the list(of string) I sent it. And yes, the list contains all my strings. The problem needs to be that the ListBox isn't refreshing?
Part of the interface:
Public Interface IPlugIn
Property PlugInName as string
Property Files As List(Of String)
End Interface
Code in the MainForm Button:
Dim currentPlugIn As Contract.API.IPlugIn
currentPlugIn = PlugIns.Find(Function(x) x.PlugInName = "Test")
currentPlugIn.Files = IO.Directory.GetFiles("SomeFolder").ToList
Code in the PlugIn:
<Export(GetType(Contract.API.IPlugIn))> _
Public Class UserControl1
Implements System.ComponentModel.INotifyPropertyChanged, Contract.API.IPlugIn
Public Property Files As System.Collections.Generic.List(Of String) Implements
Contract.API.IPlugIn.Files
Get
If IsNothing(_files) Then
_files = New List(Of String)
End If
Return _files
End Get
Set(value As System.Collections.Generic.List(Of String))
_files = value
OnPropertyChanged("Files")
End Set
End Property
Public Event PropertyChanged(sender As Object, e As
System.ComponentModel.PropertyChangedEventArgs) Implements
System.ComponentModel.INotifyPropertyChanged.PropertyChanged
Public Sub OnPropertyChanged(propertyName As String)
RaiseEvent PropertyChanged(Me, New
ComponentModel.PropertyChangedEventArgs(propertyName))
End Sub
Code in PlugIn XAML:
<ListBox Name="lstFiles" ItemsSource="{Binding Path=Files}"/>
What's the problem? I searched the Internet for examples and found hundreds, but none of them is showing how to do what I want to do. Just before posting my question here, I added the INotifyPropertyChanged, it didn't resolved the problem. Would it be better for me to use PRISM, Caliburn.Micro or is MEF only going to be okay?
Thanks for your help!
Thanks everyone!
I finally found the answer.
I implemented PRISM EventAggregator and did change the following
In the Interface and PlugIns
Completely removed
Public ReadOnly Property PlugInUI As System.Windows.Controls.UserControl Implements
Contract.API.IPlugIn.PlugInUI
Get
Dim myUI As New UserControl1
Return myUI
End Get
End Property
In the Host
Changed
Public Sub OnImportsSatisfied() Implements
System.ComponentModel.Composition.IPartImportsSatisfiedNotification.OnImportsSatisfied
For Each plugInItem In WidgetList
Desktop.Children.Add(plugInItem.PlugInView)
Next
End Sub
to
Public Sub OnImportsSatisfied() Implements
System.ComponentModel.Composition.IPartImportsSatisfiedNotification.OnImportsSatisfied
For Each plugInItem In WidgetList
Desktop.Children.Add(plugInItem)
Next
End Sub
And now everything works as I wanted!
Athari, thanks for your comment.
I did fix the properties to ObservableCollection, but didn't fix the problem. Seems I'm missing something else.
Here's the full code.
Interface
Namespace API
Public Interface IPlugIn
Property Files As System.Collections.ObjectModel.ObservableCollection(Of String)
ReadOnly Property PlugInUI As System.Windows.Controls.UserControl
End Interface
End Namespace
PlugIn
Imports System.ComponentModel.Composition
<Export(GetType(Contract.API.IPlugIn))> _
Public Class UserControl1
Implements Contract.API.IPlugIn, System.ComponentModel.INotifyPropertyChanged,
System.Collections.Specialized.INotifyCollectionChanged
Private _files As System.Collections.ObjectModel.ObservableCollection(Of String)
Public Property Files As System.Collections.ObjectModel.ObservableCollection(Of String)
Implements Contract.API.IPlugIn.Files
Get
If IsNothing(_files) Then
_files = New System.Collections.ObjectModel.ObservableCollection(Of String)
End If
Return _files
End Get
Set(value As System.Collections.ObjectModel.ObservableCollection(Of String))
_files = value
OnPropertyChanged("Files")
OnCollectionChanged(New
Collections.Specialized.NotifyCollectionChangedEventArgs _
(Specialized.NotifyCollectionChangedAction.Reset))
End Set
End Property
Public ReadOnly Property PlugInUI As System.Windows.Controls.UserControl Implements
Contract.API.IPlugIn.PlugInUI
Get
Dim myUI As New UserControl1
Return myUI
End Get
End Property
Public Event PropertyChanged(sender As Object, e As
System.ComponentModel.PropertyChangedEventArgs) Implements
System.ComponentModel.INotifyPropertyChanged.PropertyChanged
Public Sub OnPropertyChanged(Optional propertyName As String = Nothing)
RaiseEvent PropertyChanged(Me, New
ComponentModel.PropertyChangedEventArgs(propertyName))
End Sub
Public Event CollectionChanged(sender As Object, e As
System.Collections.Specialized.NotifyCollectionChangedEventArgs) Implements
System.Collections.Specialized.INotifyCollectionChanged.CollectionChanged
Public Sub OnCollectionChanged(args As
System.Collections.Specialized.NotifyCollectionChangedEventArgs)
RaiseEvent CollectionChanged(Me, args)
End Sub
End Class
PlugIn XAML
<Grid>
<ListBox Height="248" HorizontalAlignment="Left" Margin="30,31,0,0" Name="ListBox1"
VerticalAlignment="Top" Width="241" ItemsSource="{Binding Files}">
</Grid>
Host App
Imports System.ComponentModel.Composition
Imports System.ComponentModel.Composition.Hosting
Public Class HostApp
<ImportMany(GetType(Contract.API.IPlugIn))> _
Public Property PlugIns As List(Of Contract.API.IPlugIn)
Private _container As CompositionContainer
Public Sub Compose()
Dim catalog As New AggregateCatalog
catalog.Catalogs.Add(New DirectoryCatalog("pluginfolder"))
_container = New CompositionContainer(catalog)
_container.ComposeParts(Me)
End Sub
Public Sub LoadPlugIns()
Compose()
For Each item In PlugIns
Desktop.Children.Add(item.PlugInUI)
Next
End Sub
Private Sub HostApp_Loaded(sender As Object, e As System.Windows.RoutedEventArgs)
Handles Me.Loaded
LoadPlugIns()
End Sub
Private Sub Button1_Click(sender As System.Object, e As System.Windows.RoutedEventArgs)
Handles Button1.Click
Dim fileList As New Collections.ObjectModel.ObservableCollection(Of String)
For Each fileItem As String In IO.Directory.GetFiles("Somefolder")
fileList.Add(fileItem)
Next
PlugIns.Item(0).Files = fileList
End Sub
End Class
I need the Files property to be listed in the PlugIn ListBox.
Thanks again for your help!
Related
As you can see in the code below I have created a custom control named Period that inherits from Listbox. In it I have declared a read-only dependancy property named 'Subjects'. When a single Period is placed on the WPF window everything runs fine. However, when I place more than one I get the error mentioned in the title.
Here is the Period Class:
Public Class Period
Inherits System.Windows.Controls.ListBox
'-------- PROPERTIES --------'
Public ReadOnly Property Subjects() As ObservableCollection(Of Subject)
Get
Return Me.GetValue(SubjectsProperty)
End Get
End Property
Private ReadOnly SubjectsPropertyKey As DependencyPropertyKey = DependencyProperty.RegisterReadOnly("Subjects", GetType(ObservableCollection(Of Subject)), GetType(Period), New FrameworkPropertyMetadata(New ObservableCollection(Of Subject)))
Public ReadOnly SubjectsProperty As DependencyProperty = SubjectsPropertyKey.DependencyProperty
'-------- SUBROUTINES ---------'
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(Period), New FrameworkPropertyMetadata(GetType(Period)))
End Sub
Public Sub New()
MyBase.New()
Me.SetValue(SubjectsPropertyKey, New ObservableCollection(Of Subject))
End Sub
'-------- METHODS ---------'
Public Sub AddSubject(ByRef subject As Subject)
If Me.CheckForDuplicates(subject) = True Then
MsgBox("This subject is already present in this period.")
Else
Dim SubjectsList As New ObservableCollection(Of Subject)
SubjectsList = Me.GetValue(SubjectsProperty)
SubjectsList.Add(subject)
Me.SetValue(SubjectsPropertyKey, SubjectsList)
End If
End Sub
Public Sub RemoveSubject(ByRef subject As Subject)
If Me.CheckForDuplicates(subject) = False Then
MsgBox("This subject is not present in this period.")
Else
Dim SubjectsList As New ObservableCollection(Of Subject)
SubjectsList = Me.GetValue(SubjectsProperty)
SubjectsList.Remove(subject)
Me.SetValue(SubjectsPropertyKey, SubjectsList)
End If
End Sub
Public Function CheckForDuplicates(ByRef subject As Subject) As Boolean
Dim Conflict As Boolean
If Subjects.Contains(subject) Then
Conflict = True
End If
Return Conflict
End Function
Private Sub Period_Initialized(sender As Object, e As EventArgs) Handles Me.Initialized
Me.ItemsSource = Subjects
End Sub
End Class
Here is the code for the window:
<Grid Background="#FF2B2B2B">
<local:Period HorizontalAlignment="Left" VerticalAlignment="Top"/>
<local:Period HorizontalAlignment="Left" VerticalAlignment="Top"/>
</Grid>
SubjectsPropertyKey and SubjectsProperty has to be Shared. Otherwise it tries to register Subject with each instantiation of Period class and this fails after a successful registering.
See the Examples section in MSDN.
I am going to have a variety of lists that I am going to bind to a variety of comboboxes so I created a class called 'myValueList'
Imports MySql.Data.MySqlClient
Public Class myValueList
Public Property list As New List(Of myValueItem)
Public Class myValueItem
Public Property displayString As String
Public Property valueString As String
End Class
Public Sub fill(ByVal mySqlString As String)
Dim myconn As New myLib.mySQL
myconn.setConnectionString("aUser", "aPassword", "aServer", "aDatabase")
myconn.open()
Dim myReader As MySqlDataReader = myconn.sendquery(mySqlString)
Do While myReader.Read
list.Add(New myValueItem() With {.displayString = myReader(0), .valueString = myReader(1)})
Loop
myconn.closeDispose()
End Sub
End Class
And then I am trying to bind it to the combobox:
<ComboBox Height="22" Name="ComboBox54" Width="120" HorizontalAlignment="Left" ItemsSource="{Binding Path=my_list}" DisplayMemberPath="displayString" SelectedValuePath="valueString"/>
Here is my mainwindow class:
Class MainWindow
Private Sub Window_Loaded(sender As System.Object, e As System.Windows.RoutedEventArgs) Handles MyBase.Loaded
Dim myI As New initial
Me.DataContext = myI
End Sub
End Class
Here is my initial class:
Public Class initial
Public Property my_list As New myValueList
Public Sub New()
my_list.fill("SELECT * from aTable")
End Sub
End Class
Other bindings in this app are working so I suspect it is the way I am going about creating this list and then binding it. Any help / insight would be greatly appreciated.
Your Path is set to my_list (wich is an instance of myValueList), but the property on your class is named list, you should bind to my_list.list, which is the actual enumeration.
i'm currently experimenting wpf communication between two views of different modules using prism IEventAggregator. publishing module and subscribing module is working fine, for some reason i can't understand why the subscriber UI is not updating.i place a button to display a msgbox at the subscriber module just to be sure that it receives it and it does. and i think i properly implement the INotifyPropertyChanged.
if i place subscribe in subscriber view's code-behind it works as i want it to be....do i do it the wrong way? please correct me. thanks.
a separate class for module message passing. this class is from this post http://www.shujaat.net/2010/12/wpf-eventaggregator-in-prism-40-cal.html
Public Class SendServices
Public Shared Property SendMessage As EventAggregator
Shared Sub New()
SendMessage = New EventAggregator
End Sub
End Class
Publisher :
Public Class Module1ViewModel
Private _msgsend As String
Public WriteOnly Property MessageSend As String
Set(value As String)
_msgsend = value
End Set
End Property
Public Sub Send()
SendServices.SendMessage.GetEvent(Of SendStringEvent).Publish(New SendString With {.Name = _msgsend})
End Sub
End Class
Subscriber :
Public Class Module2ViewModel
Implements INotifyPropertyChanged
Private _receivedMSG As String
Public Property ReceivedMSG As String
Get
Return _receivedMSG
End Get
Set(value As String)
_receivedMSG = value
OnPropertyChanged("ReceivedMSG")
End Set
End Property
'Binded to subscriber View button using interactions
Public Sub Received()
MsgBox(ReceivedMSG)
End Sub
Private Sub ReceivedMessage(msg As SendString)
_receivedMSG = msg.Name
End Sub
Public Sub New()
SendServices.SendMessage.GetEvent(Of SendStringEvent)().Subscribe(AddressOf ReceivedMessage, ThreadOption.UIThread, False)
End Sub
Public Event PropertyChanged As PropertyChangedEventHandler Implements INotifyPropertyChanged.PropertyChanged
Protected Sub OnPropertyChanged(ByVal name As String)
RaiseEvent PropertyChanged(Me, New PropertyChangedEventArgs(name))
End Sub
End Class
Subscriber View code-behind
Public Class Module2View
Sub New()
InitializeComponent()
Me.DataContext = New Module2ViewModel
End Sub
End Class
and the binding part to display the message
<TextBox Height="23" HorizontalAlignment="Left" Margin="111,39,0,0" Name="TextBox1" VerticalAlignment="Top" Width="158" Text="{Binding Path=ReceviedMSG}"/>
You don't fire the OnPropertyChanged event because you are directly setting the field _receivedMSG in your handler which is bypassing the property setter which fires the event.
So you should use the property setter instead:
Private Sub ReceivedMessage(msg As SendString)
ReceivedMSG = msg.Name
End Sub
Can anyone suggest approaches that will allow me to change the properties of a host WinForm from a WPF element in an ElementHost?
I have an MVVM application using Prism that's bound to an ElementHost. I would like to change the WinForm title, resize the WinForm and close the WinForm from within my ViewModel.
I understand receiving data from a WPF composite control described in this article but I can't see how that will work in a ViewModel.
App is the WinForm
ChartWizardViewModel is the ViewModel I want to change the properties of App from.
Everything else is the Prism architecture. Shell, Bootstrapper etc.
Public Class App
Public Sub New(ByVal modulesToLoad As List(Of String))
' This call is required by the designer.
InitializeComponent()
If Application.Current Is Nothing Then
Dim wpfAppAdapter As New WpfApplicationAdapter
End If
' Load the application modules
Dim formBootstrapper As New Bootstrapper(modulesToLoad)
formBootstrapper.Run()
' Get the current instance of shell and bind it to the ElementHost
Dim shellElement = formBootstrapper.Container.Resolve(Of Shell)()
ehMaster.Child = shellElement
End Sub
End Class
Public NotInheritable Class Bootstrapper
Inherits UnityBootstrapper
Private _modulesToLoad As List(Of String) ' The modules that we want to load
Public Sub New(ByVal modulesToLoad As List(Of String))
_modulesToLoad = modulesToLoad
End Sub
Protected Overrides Function CreateShell() As DependencyObject
Dim shell = Container.Resolve(Of Shell)()
Return shell
End Function
Protected Overrides Function GetModuleCatalog() As IModuleCatalog
Dim catalog As ModuleCatalog = New ConfigurationModuleCatalog()
For Each moduleToLoad As String In _modulesToLoad
Select Case StringHelper.CleanString(moduleToLoad)
Case "chartwizardmodule"
catalog.AddModule(GetType(ChartWizardModule))
End Select
Next
Return catalog
End Function
End Class
Public NotInheritable Class ChartWizardModule
Implements IModule
Private ReadOnly regionManager As IRegionManager
Public Sub Initialize() Implements IModule.Initialize
regionManager.RegisterViewWithRegion("MainRegion", GetType(MainWindow))
End Sub
Public Sub New(regionManager As IRegionManager)
Me.regionManager = regionManager
End Sub
End Class
Partial Public Class MainWindow
Private _objChartWizardViewModel As ChartWizardViewModel ' The chart wizard base view model that controls the rest of the views
Private Sub MainWindow_Loaded(ByVal sender As Object, ByVal e As System.Windows.RoutedEventArgs) Handles Me.Loaded
Dim objChartWizardViewModel As New ChartWizardViewModel()
_objChartWizardViewModel = objChartWizardViewModel
' Data Context for the Chart Wizard
Me.DataContext = _objChartWizardViewModel
End Sub
End Class
Public Class ChartWizardViewModel
Implements INotifyPropertyChanged
' I need to change the properties of the WinForm (App) from here
End Class
I solved this with an EventAggregator as discussed in this post
Using EventAggregator to communicate between WPF in ElementHost and host WinForm
I've searched as best as I can, and I can't find an answer to this specific problem that I have... WPF binding seems to be great and all, but I end up banging my head against the wall more often than not.
Okay, I have a singleton class which is ultimately the one that I'm binding to:
Imports System.ComponentModel
Imports System.Collections.ObjectModel
Public Class AmandaSeyfried
Implements INotifyPropertyChanged
Shared _config As New config
Public Event PropertyChanged(sender As Object, E As PropertyChangedEventArgs) Implements INotifyPropertyChanged.PropertyChanged
Protected Overridable Sub OnPropertyChanged(propertyName As String)
RaiseEvent PropertyChanged(Me, New PropertyChangedEventArgs(propertyName))
End Sub
Private Shared _thisInstance As AmandaSeyfried
Protected Sub New()
' initialization goes here
End Sub
Public Shared Function GetSingleton() As AmandaSeyfried
' initialize object if it hasn't already been done
If _thisInstance Is Nothing Then
_thisInstance = New AmandaSeyfried
End If
' return the initialized instance
Return _thisInstance
End Function
Public Class CountryTranslation
Implements INotifyPropertyChanged
Private Property _englishCountryName As String = ""
Public Property EnglishCountryName As String
Get
Return _EnglishCountryName
End Get
Set(value As String)
If _englishCountryName <> value Then
_englishCountryName = value
RaiseEvent PropertyChanged(Me, New PropertyChangedEventArgs("EnglishCountryName"))
End If
End Set
End Property
Private Property _foreignCountryName As String = ""
Public Property ForeignCountryName As String
Get
Return _foreignCountryName
End Get
Set(value As String)
If _foreignCountryName <> value Then
_foreignCountryName = value
RaiseEvent PropertyChanged(Me, New PropertyChangedEventArgs("ForeignCountryName"))
End If
End Set
End Property
Public Event PropertyChanged(sender As Object, e As System.ComponentModel.PropertyChangedEventArgs) Implements System.ComponentModel.INotifyPropertyChanged.PropertyChanged
End Class
Private WithEvents _countryTranslations As New ObservableCollection(Of CountryTranslation)
Public Property CountryTranslations As ObservableCollection(Of CountryTranslation)
Get
If _config.GetKeyTextValue("countryTranslations") <> "" Then
Dim reader As New IO.StringReader(_config.GetKeyTextValue("countryTranslations"))
Dim Serializer As New Xml.Serialization.XmlSerializer(_countryTranslations.GetType)
_countryTranslations = Serializer.Deserialize(reader)
End If
Return _countryTranslations
End Get
Set(value As ObservableCollection(Of CountryTranslation))
_countryTranslations = value
RaiseEvent PropertyChanged(Me, New PropertyChangedEventArgs("CountryTranslations"))
End Set
End Property
Private Sub CountryTranslationCollectionChanged(sender As Object, e As Specialized.NotifyCollectionChangedEventArgs) Handles _countryTranslations.CollectionChanged
Dim newStringWriter As New IO.StringWriter
Dim NewSerializer As New Xml.Serialization.XmlSerializer(_countryTranslations.GetType)
NewSerializer.Serialize(newStringWriter, _countryTranslations)
_config.SaveKeyTextValue("countryTranslations", newStringWriter.ToString)
RaiseEvent PropertyChanged(Me, New PropertyChangedEventArgs("CountryTranslations"))
End Sub
End Class
_config is a misnamed helper class that stores and retrieves data from a local SqlCe instance. Essentially the object is serialized, stored in the DB, and then pulled out of the DB any times it's needed and deserialized back into an object. All in all, it seems to be working fairly well.
My problem is that although I can bind to the object, and I can monitor when a row is added in a WPF DataGrid via the CollectionChangedMethod handler, I don't get any notification when either of the two properties of CountryTranslation are changed.
The rest of my related code is... XAML... there's obviously more, but I don't believe the XAML portion of the binding is to blame, so I'll trim it to the relevant:
<toolkit:DataGrid Margin="12,12,12,12" ItemsSource="{Binding Path=KarenSmith.CountryTranslations, Mode=TwoWay}" AutoGenerateColumns="False" HorizontalAlignment="Stretch" Width="Auto" SelectionMode="Single">
<toolkit:DataGrid.Columns>
<toolkit:DataGridTextColumn Width="283" Binding="{Binding EnglishCountryName,Mode=TwoWay}" />
<toolkit:DataGridTextColumn Width="283" Binding="{Binding ForeignCountryName,Mode=TwoWay}" />
</toolkit:DataGrid.Columns>
</toolkit:DataGrid>
And the nice and simple code-behind:
Public Class Preferences
Public Property KarenSmith As AmandaSeyfried = AmandaSeyfried.GetSingleton
Sub New()
' This call is required by the designer.
InitializeComponent()
' Add any initialization after the InitializeComponent() call.
DataContext = Me
End Sub
Private Sub Close_Click(sender As System.Object, e As System.Windows.RoutedEventArgs)
Me.Close()
End Sub
End Class
If I throw some break points on the Getter and Setters of the CountryTranslation class, I can monitor when they're being changed (via the datagrid, so binding is working), but try as I might I can't figure out how to raise an event based upon that back in the main class to subsequently update the datastore to show the changes.
Usually I add a CollectionChanged event to the ObservableCollection, which attaches a PropertyChanged event to it's items when they get added, and that event listener listens for changes and would handle them as needed.
Here's an example: (hope the syntax is correct since I just ran it through a C# to VB.Net converter)
Public Sub New()
AddHandler MyCollection.CollectionChanged, AddressOf MyCollection_CollectionChanged
End Sub
Private Sub MyCollection_CollectionChanged(sender As Object, e As CollectionChangedEventArgs)
If e.NewItems IsNot Nothing Then
For Each item As MyItem In e.NewItems
AddHandler item.PropertyChanged, AddressOf MyItem_PropertyChanged
Next
End If
If e.OldItems IsNot Nothing Then
For Each item As MyItem In e.OldItems
RemoveHandler item.PropertyChanged, AddressOf MyItem_PropertyChanged
Next
End If
End Sub
Private Sub MyItem_PropertyChanged(sender As Object, e As PropertyChangedEventArgs)
If e.PropertyName = "Some Property" Then
DoWork()
End If
End Sub
The C# version looks like this:
public MyViewModel()
{
MyCollection.CollectionChanged += MyCollection_CollectionChanged;
}
void MyCollection_CollectionChanged(object sender, CollectionChangedEventArgs e)
{
if (e.NewItems != null)
foreach(MyItem item in e.NewItems)
item.PropertyChanged += MyItem_PropertyChanged;
if (e.OldItems != null)
foreach(MyItem item in e.OldItems)
item.PropertyChanged -= MyItem_PropertyChanged;
}
void MyItem_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
if (e.PropertyName == "Some Property")
DoWork();
}