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?
CodePudding user response:
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()