WPF Custom Control and exposing properties thru DependencyProperty - wpf

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

Related

Make WPF-control shared

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.

How to create a custom OpenFiledialog Property for a UserControl

When I right click a UserControl and select Properties, I want to create a custom property inside the UserControl properties that loads a file from an OpenFileDialog, like the ColumnDefinitions, but that browses inside my machine:
How can I achieve that? I've been searching but I'm a bit lost on where to start from.
NOTE: The image indicates that the property I want to create is one of the properties of the UserControl that appear when you Right click->Properties the UserControl.
Thanks!
I declared a property for searching execuble files in an OpenFileDialog in a WinForm project. The code is in VB .NET.
First create a Class like this:
Imports System.Drawing.Design
Imports System.Windows.Forms.Design
Imports System.Windows.Forms
Public Class ExecutableEditor : Inherits UITypeEditor
Public Overrides Function GetEditStyle(context As System.ComponentModel.ITypeDescriptorContext) As System.Drawing.Design.UITypeEditorEditStyle
Return UITypeEditorEditStyle.Modal
End Function
Public Overrides Function EditValue(context As System.ComponentModel.ITypeDescriptorContext, provider As System.IServiceProvider, value As Object) As Object
If context Is Nothing And context.Instance Is Nothing And provider Is Nothing Then
Return value
End If
Dim editor_service As IWindowsFormsEditorService = _
CType(provider.GetService(GetType(IWindowsFormsEditorService)), _
IWindowsFormsEditorService)
If editor_service Is Nothing Then Return value
Dim ofdEjecutable As New OpenFileDialog
ofdEjecutable.Title = "Select an executable file"
ofdEjecutable.FileName = Nothing
ofdEjecutable.Filter = "executable file|*.exe"
If ofdEjecutable.ShowDialog = DialogResult.OK Then
Return ofdEjecutable.FileName
Else
Return Nothing
End If
End Function
End Class
Then in the code of the UserControl declare a property like this:
Private _executable As String
<Category("Injection")> _
<EditorAttribute(GetType(ExecutableEditor), GetType(UITypeEditor))> _
Public Property Executable As String
Get
Return _executable
End Get
Set(value As String)
_executable = value
End Set
End Property
What I got from your question is that you want a Browsable Property for you user control. Going by this, for simple .net property add:
private string myString;
[Browsable(true)]
[Category("Other")]
public string MyProperty { get { return myString; } set { myString = value; } }
and in setter of the property load the file after validation.
If you want it to be dependancy property do the same but move the code of loading the file in the propertychange handler.

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

Silverlight Saving a Class Instance to Isolated Storage

Im just wondering how I go about saving an instance of a class to the silverlight isolated storage. I also need to know if it possible that the class that we save to isolated storage can have a list of instances of another class. Here is an example of the situation:
Public Class MySettingsToStore
private mPropertyA as string
Public Property PropertyA() As string
Get
Return mPropertyA
End Get
Set(ByVal value As string)
mPropertyA = value
End Set
End Property
private mlstOfSubClass as List(Of MySubClass)
Public Property lstOfSubClass() As List(Of MySubClass)
Get
Return mlstOfSubClass
End Get
Set(ByVal value As List(Of MySubClass))
mlstOfSubClass = value
End Set
End Property
End Class
Public Class MySubClass
private mPropertyA as string
Public Property PropertyA() As string
Get
Return mPropertyA
End Get
Set(ByVal value As string)
mPropertyA = value
End Set
End Property
private mPropertyB as string
Public Property PropertyB() As string
Get
Return mPropertyB
End Get
Set(ByVal value As string)
mPropertyB = value
End Set
End Property
End Class
So basically on load of the application I want to check if there is an instance of MySettingsToStore in the isolatedStorage if not I will create one and save it (and update it when needed), so the next time the application is started there will be an instance in the isolatedstorage to load.
Does anyone know how I go about this? Hope someone can help. Thanks in advance
One word: XMLSerializer
To elaborate a bit, you can serialize any class with public properties to a stream (e.g. in Isolated storage) and reverse that process on startup to load an existing file in ISO storage.
If a property is a collection of other classes, they too will be stored and restored by using XMLSerializer.

Pass data between WPF windows

I have some code that works in winforms, but not in WPF apparently, the code is as follows:
This is set globally:
Private Property avar As Object
Public main As MainWindow
Public charchoice As Char
And then in the Window Loaded sub, this is placed:
charchoice = main.charchoice
Thing is, the next window doesn't pick up this variable, so how can I make it recognise and use it? Thanks Guys
Nick
I had a similar problem and discovered that you must create a public property in the MainWindow and pass a value to the property.
Please see this example from a similar question I posted.
Hey i got same problem when i am going to pass values between two forms.
I find its solution using a simple class and Shared property.
First I create a class named with cls_pass_val which is as under:-
Public Class cls_pass_val
Private Shared var_pass_val As String = ""
Public Shared Property Pass_val() As Char
Get
Return var_pass_val
End Get
Set(ByVal value As String)
var_pass_val = value
End Set
End Property
End Class
Now at the time of assigning a value:
cls_pass_val.Pass_val='A'
and at the time of retrieving the value:
Dim var_c as Char
var_c=cls_pass_val.Pass_val

Resources