Data Validation with MVVM-Light WPF and Linq to Entity Framework - wpf

I think I have read every article google returns when I search wpf mvvm-light data validation and I dont know which way to go. I am aware of josh smith, Karl Shifflett's, and MVVM LIGHT's own demo techniques for data validation. What I see is that most validation requires me to fully "re-abstract" my model in my view model. Meaning that I have to create a property in my viewmodel for each property of my model that I want to validate (and in some cases convert all these into string values for binding/validation). This seems like a lot or redundancy when all I want to do is mark most fields as required.
I am using LINQ to entity framework(with self tracking) for my model classes which come from a SQL server DB. As a result I would prefer to keep my business data validation/rules within my viewmodels. I write a simple service interface to get the data from the model and pass it to my viewmodel.
Most of the examples I can find are from as far back as 2008 (ie josh smith). Are these techniques still valid or are there more up to date best practices for mvvm data validation with .NET 4.5 etc.
So I am asking:
1) What methods do you suggest I use
2) What methods work best in a LINQ to EF with MVVM-Light Environment.
3) EDIT: I want to provide feedback to user as they enter data, not just when they submit form
thanks

The way I do this (not necessarily correct) is to do my validation in the ViewModel (where the CRUD operations typically take place), then if there are validation errors, abort saving/adding any data and use Messenger.Default.Send to send a custom message type to my view. Then I alert the user via a DialogBox or otherwise.
I've experimented with Binding ValidationRules in the past but found by far the most reliable and consistent method to be simple if statements.

I eventually ended up using the following. I changed my model to use LINQ to self tracking entities (see this article for info about STE http://msdn.microsoft.com/en-us/library/vstudio/ff407090%28v=vs.100%29.aspx).
LINQ to STE creates an OnPropertyChanged event that implements the iNotifyPropertyChanged interface.
I just created a public partial class for the matching model object (linq entity generated code) I wanted and added an event handler for the OnPropertyChanged event. I then used the IDataErrorInfo interface to validate and throw errors as I needed. This allows me to validate the fields as they change which gets reflected to the user. This also allows you to perform more advanced validation logic that may need to requery the database (i.e. to look for if a username is already used etc.) or throw a dialog box
Also, having the data validation in the model allows me to still have validation if i perform direct "batch" operations that bypass the UI.
I then used an HasErrors and HasChanges property and used them to create a Boolean value that gets attached to the relay commands, disabling the crud command buttons if errors are present.
I will post some simple code to outline what I just described, comment if you want more detail.
Here is the Entity Framework extension of the model class:
Imports System.ComponentModel
Partial Public Class client
Implements IDataErrorInfo
#Region "Properties / Declarations"
'Collection / error description
Private m_validationErrors As New Dictionary(Of String, String)
Private _HasChanges As Boolean = False
''Marks object as dirty, requires saving
Public Property HasChanges() As Boolean
Get
Return _HasChanges
End Get
Set(value As Boolean)
If Not Equals(_HasChanges, value) Then
_HasChanges = value
OnPropertyChanged("HasChanges")
End If
End Set
End Property
'Extends the class with a property that determines
'if the instance has validation errors
Public ReadOnly Property HasErrors() As Boolean
Get
Return m_validationErrors.Count > 0
End Get
End Property
#End Region
#Region "Base Error Objects"
'Returns an error message
'In this case it is a general message, which is
'returned if the list contains elements of errors
Public ReadOnly Property [Error] As String Implements System.ComponentModel.IDataErrorInfo.Error
Get
If m_validationErrors.Count > 0 Then
Return "Client data is invalid"
Else
Return Nothing
End If
End Get
End Property
Default Public ReadOnly Property Item(ByVal columnName As String) As String Implements System.ComponentModel.IDataErrorInfo.Item
Get
If m_validationErrors.ContainsKey(columnName) Then
Return m_validationErrors(columnName).ToString
Else
Return Nothing
End If
End Get
End Property
#End Region
#Region "Base Error Methods"
'Adds an error to the collection, if not already present
'with the same key
Private Sub AddError(ByVal columnName As String, ByVal msg As String)
If Not m_validationErrors.ContainsKey(columnName) Then
m_validationErrors.Add(columnName, msg)
End If
End Sub
'Removes an error from the collection, if present
Private Sub RemoveError(ByVal columnName As String)
If m_validationErrors.ContainsKey(columnName) Then
m_validationErrors.Remove(columnName)
End If
End Sub
#End Region
Public Sub New()
Me.HasChanges = False
End Sub
#Region "Data Validation Methods"
''handles event and calls function that does the actual validation so that it can be called explicitly for batch processes
Private Sub ValidateProperty(ByVal sender As Object, ByVal e As PropertyChangedEventArgs) Handles Me.PropertyChanged
If e.PropertyName = "HasChanges" Then
Exit Sub
End If
IsPropertyValid(e.PropertyName)
HasChanges = True
End Sub
Public Function IsPropertyValid(sProperty As String) As Boolean
Select Case sProperty
''add validation by column name here
Case "chrLast"
If Me.chrLast.Length < 4 Then
Me.AddError("chrLast", "The last name is too short")
Return True
Else
Me.RemoveError("chrLast")
Return False
End If
Case Else
Return False
End Select
End Function
#End Region
End Class
then in the view model I included the following code to bind thecommand and evaluate whether or not it can be executed.
Public ReadOnly Property SaveCommand() As RelayCommand
Get
If _SaveCommand Is Nothing Then
_SaveCommand = New RelayCommand(AddressOf SaveExecute, AddressOf CanSaveExecute)
End If
Return _SaveCommand
End Get
End Property
Private Function CanSaveExecute() As Boolean
Try
If Selection.HasErrors = False And Selection.HasChanges = True Then
Return True
Else
Return False
End If
Catch ex As Exception
Return False
End Try
End Function
Private Sub SaveExecute()
''this is my LINQ to Self Tracking Entities DataContext
FTC_Context.SaveChanges()
End Sub
the following is how I bound my button (has custom styling in WPF)
<Button Content="" Height="40" Style="{DynamicResource ButtonAdd}" Command="{Binding SaveCommand}" Width="40" Cursor="Hand" ToolTip="Save Changes" Margin="0,0,10,10"/>
so, when there are no validation errors and the current client record "isDirty" the save button automatically becomes enabled, and disabled if any of those two conditions fail. This way I now have a simple way of validating any type of column/data I want for the entity, and I can provide user feedback as they enter data in the form, and only enable CRUD command buttons once all my "conditions" have been met.
This was quite a battle to figure out.

Related

Implementing a message suppression system with settings serialization to XML

In my application, I need a system where some messages shown to the user have a "do not show again" checkbox. In the Settings menu, I want to have a section "dismissed messages" with names of messages and a checkbox by each, so the user can un-dismiss them if needed. If the message is dismissed, the last choice user selected in the message dialog should become a default one.
Additionally, settings should be saved to an XML file - including the message suppression state and the default choice. My current solution is very crude, and I'd like to know if there is a better way.
The message class is defined as:
Public Class Message
Public Property title As String
Public Property content As String
Public Property buttons As Utilities.Enums.messageButtonTypes 'Ok/Cancel, Yes/No, etc.
Public Property allowDoNotShowAgain As Boolean = False 'whether the message is dismissable
Public Property doNotShowAgain As Boolean = False 'the actual dismiss state
Public Property result As Boolean
Public Property rememberedResult As Boolean 'last user choice if the message is dismissed
End Class
Specific messages are initialized in the MSG module:
Module Msg
'This message is not dismissable
Public connectionNotEstablished As New Message() With {
.title = "Connection not established",
.content = "Connection not established. Please check if the host application is running.",
.buttons = Utilities.Enums.messageButtonTypes.Ok
}
'This message is dismissable
Public noResultsPlotsDefined As New Message() With {
.title = "No plots defined",
.content = "You have not defined any plots. Would you like to run the study anyway?",
.buttons = Utilities.Enums.messageButtonTypes.YesNo,
.allowDoNotShowAgain = True
}
'Just a list to store references to all the messages for binding, looping, etc.
Public allMessages As New List(Of Message) From {
connectionNotEstablished,
noResultsPlotsDefined
}
Public Function ShowMessage(message As Message) As Boolean
If message.doNotShowAgain Then message.result = message.rememberedResult : Return message.rememberedResult 'If message is dismissed, return the last user choice
Dim messageDialog As New MessageDialog(message.title, message.content, message.buttons, message.allowDoNotShowAgain, message.defaultButtonCustomCaption, message.cancelButtonCustomCaption)
message.result = messageDialog.ShowDialog()
message.doNotShowAgain = messageDialog.doNotShowAgain
If message.doNotShowAgain Then message.rememberedResult = message.result
Return message.result
End Function
End Module
Specific messages are called in various functions, for example, like this:
Msg.ShowMessage(connectioNotEstablished)
So far, this is easy enough - very convenient to use while building my application. Now, the bit that I'm not sure about - the AppSettings class. Like I said, I need to store some of the properties of each message, so that I can WPF-bind to the message list in the settings window. Right now, the AppSettings has a reference to the MSG class messages list:
Public Class AppSettings
Public Property messages As List(Of Message) = Msg.allMessages
Public Sub SaveToDefaultPath()
Save(Constants.Paths.settingsFilePath)
End Sub
Private Sub Save(ByVal filename As String)
Using sw As StreamWriter = New StreamWriter(filename)
Dim xmls As XmlSerializer = New XmlSerializer(GetType(AppSettings))
xmls.Serialize(sw, Me)
End Using
End Sub
Private Function Read(ByVal filename As String) As AppSettings
Using sw As StreamReader = New StreamReader(filename)
Dim xmls As XmlSerializer = New XmlSerializer(GetType(AppSettings))
Return TryCast(xmls.Deserialize(sw), AppSettings)
End Using
End Function
End Class
In my settings WPF window, I can then bind to the messages property, and choose to show the title as TextBlock, doNotShowAgain as a CheckBox, and rememberedResult as a ComboBox. I haven't done that bit yet, but I think it should be pretty straightforward with current application architecture.
Problem is the serialization and de-serialization to and from XML (see the last two functions of the AppSettings class). Since this class stores the references to the whole messages list, which has not just title, doNotShowAgain and rememberedResult, but also the message content and it's other properties, which really clutter that XML file.
I am not sure how to solve this. I could, perhaps, store only the required variables of each message in the AppSettings, but that would require some kind of two-way converter or something. And by this point I'm starting to doubt if this is really the right way to achieve what I need.
I can't be the first one implementing this, so maybe there is a convention for such a thing. Any suggestions?
EDIT: while waiting for answers, I have successfully implemented saving the message dismiss states to XML - unfortunately, it saves the whole message class data, instead of just the title, doNotShowAgain and rememberedResult. To make this work, I had to make only one small change - the property messages in AppSettings was declared as an Array rather than as a List, so that the XML deserializer wouldn't append messages to that List, but instead, would just replace it as a whole.
Public Class AppSettings
Public Property Messages() As Message() = Msg.allMessages.ToArray()
...
End Class
So while this works (binding these messages to WPF window also works), the XML file is cluttered with unnecessary values for each message, for example:
<Message>
<title>No plots defined</title>
<content>You have not defined any plots. Would you like to run the study anyway?</content>
<buttons>YesNo</buttons>
<allowDoNotShowAgain>true</allowDoNotShowAgain>
<doNotShowAgain>false</doNotShowAgain>
<result>false</result>
<rememberedResult>false</rememberedResult>
</Message>
But for this use case, it would be more than enough to have only this bit for each message in the XML file:
<Message>
<title>No plots defined</title>
<doNotShowAgain>false</doNotShowAgain>
<rememberedResult>false</rememberedResult>
</Message>
So my question remains - what is the best solution here? Am I even in the ballpark?
It appears your only problem is having clutter in the Xml file. So you can tell the serializer to ignore certain properties with <XmlIgnore>
Public Class Message
Public Property title As String
<XmlIgnore>
Public Property content As String
<XmlIgnore>
Public Property buttons As Utilities.Enums.messageButtonTypes 'Ok/Cancel, Yes/No, etc.
<XmlIgnore>
Public Property allowDoNotShowAgain As Boolean = False 'whether the message is dismissable
Public Property doNotShowAgain As Boolean = False 'the actual dismiss state
<XmlIgnore>
Public Property result As Boolean
Public Property rememberedResult As Boolean 'last user choice if the message is dismissed
End Class
The serializer will neither serialize nor deserialize those properties.
Now you can also serialize a subset of messages defined by linq query, like this
<XmlIgnore>
Public Property messages As List(Of Message) = Msg.allMessages
<XmlElement("messages")>
Public Property messagesAllowDoNotShowAgain As List(Of Message) = Msg.allMessages.Where(Function(m) m.allowDoNotShowAgain).ToList()

Winforms Data from database across multiple forms

I have a winforms applications that has an ms sql server backend. In my database i have lookup tables for like status, and other tables where the data rarely changes. In my application, several forms might use the same lookup tables (Some have a lot of data in them). Instead of loading/filling the data each time the form is open, is there a way to cache the data from the database that can be accessed from multiple forms. I did some searching, but couldnt find the best solution. There is caching, dictionaries, etc. What is the best solution and can you point me to the documentation that discusses it and may even have an example.
Edit:
In my original post I failed to mention that I have a strongly typed dataset and use tableadapters. I want to preload my lookup tables when my application starts, and then have these dataset tables be used throughout the application, on multiple forms without having to fill them on every form.
I have tried creating a class:
Public Class dsglobal
Public Shared EML_StaffingDataSet As EML_StaffingDataSet
Public Shared Sub populateDS()
EML_StaffingDataSet = New EML_StaffingDataSet
End Sub
Public Shared Sub loadskills()
Dim ta As New EML_StaffingDataSetTableAdapters.TSTAFFSKILLTableAdapter
ta.Fill(EML_StaffingDataSet.TSTAFFSKILL)
End Sub
End Class
I run this on a background worker when my application is starting up. So it loads the dataset table. On fill, I can see the datatable has data in it. When I open a form, i want to use the dataset table, but it seems to clear the data out. Not sure if my approach is correct or where my error is.
Edit2:
I have also tried this per comments, but not sure I am doing it correctly. If I am doing it correctly, then how do I use that as a datasource at design time, can i only do that programmatically?
Public Module lookupdata
Private EML_StaffingDataSet As EML_StaffingDataSet
Private skillvalues As List(Of skill)
Public ReadOnly Property skill As List(Of skill)
Get
If skillvalues Is Nothing Then
getskillvalues()
End If
Return skillvalues
End Get
End Property
Private Sub getskillvalues()
skillvalues = New List(Of skill)
EML_StaffingDataSet = New EML_StaffingDataSet
Dim ta As New EML_StaffingDataSetTableAdapters.TSTAFFSKILLTableAdapter
ta.Fill(EML_StaffingDataSet.TSTAFFSKILL)
For Each row As DataRow In EML_StaffingDataSet.TSTAFFSKILL
Dim skill As New skill
skill.skill_id = row("skill_id")
skill.skill_desc = row("skill_desc")
skill.skill_open_ind = row("skill_open_ind")
skillvalues.Add(skill)
Next
End Sub
End Module
Public Class skill
Public Property skill_id As Integer
Public Property skill_desc As String
Public Property skill_open_ind As Boolean
End Class
You might want to consider a lazy loading pattern, like this:
Public Module LookupData
Private statusValues As List(Of LookupValue)
Public Readonly Property Statuses As List(Of LookupValue)
Get
If statusValues Is Nothing Then
GetStatusValues()
End If
Return statusValues
End Get
End Property
Private Sub GetStatusValues()
statusValues = New List(Of LookupValue)
Dim sql = "select key, value from StatusTable"
'TODO: Read the items from the database here, adding them to the list.
End Sub
End Module
Public Class LookupValue
Public Property Key As String
Public Property Value As String
End Class
The idea is that you've got a single instance of LookupData (a Module in VB, there can be only one). Lookup data has a series of Properties, each of which returns a list of values from the database. If the data has already been loaded, it just returns what it has cached. If the data has not been loaded, then the first time it is referenced it retrieves it from the database.
You can consume it elsewhere in your code as follows:
Dim myStatuses = LookupData.Statuses

How to Implement Entity Framework on this example?

I have been working thru this WPF example and am trying to hook it up to my database using Entity Framework but am confused on how to do this. Can someone please offer some guidance on how this would be done?
The code has the following in CustomerRepository.cs
static List<Customer> LoadCustomers(string customerDataFile)
{
// In a real application, the data would come from an external source,
// but for this demo let's keep things simple and use a resource file.
using (Stream stream = GetResourceStream(customerDataFile))
using (XmlReader xmlRdr = new XmlTextReader(stream))
return
(from customerElem in XDocument.Load(xmlRdr).Element("customers").Elements("customer")
select Customer.CreateCustomer(
(double)customerElem.Attribute("totalSales"),
(string)customerElem.Attribute("firstName"),
(string)customerElem.Attribute("lastName"),
(bool)customerElem.Attribute("isCompany"),
(string)customerElem.Attribute("email")
)).ToList();
}
which is where I assume the hook to the database would happen but not sure how. I can create the Model.edmx file to connect to the database but not sure how to physically get the list of customers from the database.
Also, this example uses a List of Customers but most examples I have gone through use ObservableCollection for this type of data. Is one preferable over the other and why?
TIA,
Brian Enderle
My MVVM/EF projects typically load entities directly into the ViewModels or Into light collections in the view models. I don't create any kind of custom repository to sit between them.
Generally my ViewModels do one of two things,
Retrieves data on instancing
Takes an entity as a constructor argument.
When I retrieve data on instance, I generally use a background worker class, which queries the context, puts the results in a list, and passes the list out. The Work Completed method then puts the entities into viewmodels and puts the ViewModels in a ObservableCollection.
Similar to this:
Private WithEvents GetProjectsBackgroundWorker As BackgroundWorker
Private _Projects As New ObservableCollection(Of ProjectViewModel)
Public Sub New()
GetProjectsBackgroundWorker = New BackgroundWorker
GetProjectsBackgroundWorker.RunWorkerAsync()
End Sub
Public Property Projects() As ObservableCollection(Of ProjectViewModel)
Get
Return _Projects
End Get
Set(ByVal value As ObservableCollection(Of ProjectViewModel))
_Projects = value
End Set
End Property
#Region " GetProjects"
Private Sub GetProjectsBackgroundWorker_DoWork(ByVal sender As Object, ByVal e As System.ComponentModel.DoWorkEventArgs) Handles GetProjectsBackgroundWorker.DoWork
Dim results = From proj As Project In Context.Projects Where proj.flgActive = True And proj.flgReview = True Select proj
Dim ProjList As New List(Of Project)(results)
e.Result = ProjList
End Sub
Private Sub GetProjectsBackgroundWorker_RunWorkerCompleted(ByVal sender As Object, ByVal e As System.ComponentModel.RunWorkerCompletedEventArgs) Handles GetProjectsBackgroundWorker.RunWorkerCompleted
If e.Error Is Nothing Then
For Each p As Project In e.Result
Projects.Add(New ProjectViewModel(p, Context))
Next
Else
End If
End Sub
#End Region
In ViewModels that take an entity as an argument, they often take a context as argument, especially if something is a short time-span operation. Otherwise I detach entities from the context in the even something goes hay-wire with the database or the connection is lost or something.
To answer your second question, ObservableCollections are Enumerable collections that have collection change notification implemented. The collection will notify the UI when a new item is added/removed/moved. Typically any time I have entities that are going to be viewed or displayed in the UI, I host them in an Observable Collection. Otherwise I use something simpler, a List normally.

Infragistics UltraWinGrid EmptyDataText Equivalent?

We're using Infragistics UltraWinGrid as a base class for customized controls. One of the projects that will use this control to display search results has a requirement to display a user friendly message when no matches are located.
We'd like to encapsulate that functionality into the derived control - so no customization beyond setting the message to display is required by the programmer who uses the control. This would have to be done in generic fashion - one size fits all datasets.
Is there allowance in the UltraWinGrid for this type of usage already? If so, where would I find it hidden. :-)
If this functionality needs to be coded, I can think of an algorithm which would add a blank record to whatever recordset was set and place that into the grid. In your opinion, is this the best way to handle the solution?
I don't know if this will help, but here's to finishing up the thread. I didn't find a built in way, so I solved this problem as follows: In my class which inherits UltraGrid
Public Class MyGridPlain
Inherits Infragistics.Win.UltraWinGrid.UltraGrid
I added two properties, one to specify what the developer wants to say in the empty data case, and another to enable the developer to place their message where they want it
Private mEmptyDataText As String = String.Empty
Private mEmptyDataTextLocation As Point = New Point(30, 30)Public Shadows Property EmptyDataTextLocation() As Point
Get
Return mEmptyDataTextLocation
End Get
Set(ByVal value As Point)
mEmptyDataTextLocation = value
setEmptyMessageIfRequired()
End Set
End Property
Public Shadows Property EmptyDataText() As String
Get
Return mEmptyDataText
End Get
Set(ByVal value As String)
mEmptyDataText = value
setEmptyMessageIfRequired()
End Set
End Property
I added a method which will check for empty data and set the message if so. And another method which will remove the existing empty message.
Private Sub setEmptyMessageIfRequired()
removeExistingEmptyData()
'if there are no rows, and if there is an EmptyDataText message, display it now.
If EmptyDataText.Length > 0 AndAlso Rows.Count = 0 Then
Dim lbl As Label = New Label(EmptyDataText)
lbl.Name = "EmptyDataLabel"
lbl.Size = New Size(Width, 25)
lbl.Location = EmptyDataTextLocation
ControlUIElement.Control.Controls.Add(lbl)
End If
End SubPrivate Sub removeExistingEmptyData()
'any previous empty data messages?
Dim lblempty() As Control = Controls.Find("EmptyDataLabel", True)
If lblempty.Length > 0 Then
Controls.Remove(lblempty(0))
End If
End Sub
Last - I added a check for empty data to the grid's InitializeLayout event.
Private Sub grid_InitializeLayout(ByVal sender As Object, _
ByVal e As Infragistics.Win.UltraWinGrid.InitializeLayoutEventArgs) _
Handles MyBase.InitializeLayout
setEmptyMessageIfRequired()
End Sub

SubSonic and a dateTimePicker control on a Windows Form

Question #1: Latest working Version
I'm currently using SubSonic 2.1 built 491.
Is there a later build? Where can I get it?
I know 2.2 was released but it doesn't come with a Setup and I wouldn't know how to modify the App.Config/Web.Config to work with it.
Question #2: Issue with dateTimePicker control on Windows Form.
I keep getting System.FormatException trying to retrieve data From SubSonic to that control or saving data from that control to the Database through SubSonic.
For example, if I want to save the Time only, I can use the .Text property. To save the Date, I need to use the .Value property of the control.
I've tried all sorts of conversion such as Convert.ToDateTime(dateTimePicker.Value.ToString()) and others but I can't find a consistent pattern that would not throw an exception. Any help on this would be greatly appreciated.
Re Question 1 - I don't think any app.config / web.config change is required between SS2.1 and 2.2
For Question 2 - are you using the new data types in MSSQL2008 by any chance? If so, SS2.2 doesn't seem to handle them properly yet.
Just stumbled upon your post.
I'm not sure if It was a FormatException, but I got a Exception with a databound DateTimePicker, too.
The problem is that
DateTimePicker.MinimumDateTime = #1/1/1753#
while
DateTime.MinValue = #1/1/0001#
New DateTime = #1/1#0001#
So if bind a property to a DataGridView that returns a DateTime value earlier than #1/1/1753# or later then #12/31/9998# ( DateTimePicker.MaximumDateTime ) you get an exception.
I solved this with my own inherited DateTimePicker (sorry, but it's written in vb)
To use it you can just bind your Subsonic object to the value property. But you have to set the ShowCheckBox property to true and bind a bool value to the CheckedValue property (to indicate that the date is set) which is persisted in your db, too.
Now If an empty date is returned, the date is set to Now and the checkedValue to false, leading to a disabled DateTimePicker.
If you check the Checkbox or choose a date with the calender) the checkbox is set to true and the Checkbox is checked.
Attention: Do not bind the Checked property directly, bind the CheckedValue property.
Imports System.ComponentModel
Public Class MyDateTimePicker
Inherits System.Windows.Forms.DateTimePicker
<Bindable(True)> _
Public Overloads Property Value() As DateTime
Get
If Not MyBase.Checked And (MyBase.Value < DateTimePicker.MinimumDateTime Or MyBase.Value > DateTimePicker.MaximumDateTime) Then
Return DateTime.Now
Else
Return MyBase.Value
End If
End Get
Set(ByVal value As DateTime)
If ((value < DateTimePicker.MinimumDateTime Or value > DateTimePicker.MaximumDateTime) Or value = #1/1/1900#) Then
MyBase.Value = DateTime.Now
MyBase.Checked = False
Else
MyBase.Value = value
End If
End Set
End Property
Private _CheckedValue As Boolean
<Bindable(True)> _
Public Property CheckedValue() As Boolean
Get
Return _CheckedValue
End Get
Set(ByVal value As Boolean)
_CheckedValue = value
End Set
End Property
Protected Overrides Sub OnMouseUp(ByVal e As System.Windows.Forms.MouseEventArgs)
MyBase.OnMouseUp(e)
RefreshCheckedValue()
End Sub
Protected Overrides Sub OnKeyUp(ByVal e As System.Windows.Forms.KeyEventArgs)
MyBase.OnKeyDown(e)
RefreshCheckedValue()
End Sub
Private Sub RefreshCheckedValue()
CheckedValue = Me.Checked
If Not Me.DataBindings("CheckedValue") Is Nothing Then
Me.DataBindings("CheckedValue").WriteValue()
End If
End Sub
Protected Overrides Sub OnBindingContextChanged(ByVal e As System.EventArgs)
MyBase.OnBindingContextChanged(e)
Static checkedInitialized As Boolean
If Not checkedInitialized AndAlso Not Me.DataBindings("CheckedValue") Is Nothing Then
Me.Checked = Me.CheckedValue
checkedInitialized = True
End If
End Sub
End Class

Resources