Home > Mobile >  Implementing a message suppression system with settings serialization to XML
Implementing a message suppression system with settings serialization to XML

Time:11-05

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()
  • Related