Wednesday, January 27, 2016

Displaying a message box while a splash screen is visible

If you use a splash screen while your WPF application is initializing you may have noticed a problem when you try to show a messagebox while the splash screen is visible. Perhaps the initialization process failed in Application_Startup.

The problem is that the splash screen is the active window and it is designed to disappear as soon as the first visual element is displayed. By default, message boxes are owned by the active window. You can see what happens. The message box is owned by the splash screen, the splash screen disappears as soon as the message box is displayed, so the message box disappears too.

For me, the problem is made worse because I have an unhandled exception handler defined for the application so any unhandled exception is handled by the same code whether it was raised while the splash screen was visible or later.

Private Sub Application_DispatcherUnhandledException(sender As Object, e As System.Windows.Threading.DispatcherUnhandledExceptionEventArgs) Handles Me.DispatcherUnhandledException
    ShowFatal(e.Exception.Message)
End Sub

The ShowFatal method simply displays a MessageBox. When the exception originates while the splash screen is displayed the message box disappears before the user can read it. They launch the application, something flashes, and the application exits. Not cool.

The trick is to know that the MainWindow does not yet exist during initialization. In fact, it is the creation of the main window that makes the splash screen go away. So if the main window is not yet created we display a dummy message box that attaches to the splash screen and causes the splash screen to go away. Then we display the proper message box.

Public Shared Sub ShowFatal(sMsg As String)
   ' If the main window is not yet displayed then show a dummy messagebox
    If Application.Current.MainWindow Is Nothing Then System.Windows.MessageBox.Show("")

    System.Windows.MessageBox.Show(sMsg, "Fatal Error", System.Windows.MessageBoxButton.OK, System.Windows.MessageBoxImage.Stop)
    Application.Current.Shutdown(1)

End Sub

Tuesday, January 26, 2016

Getting the client name and IP address from WCF

One of the concerns we have where I work is that users keep sharing logons and passwords. Then later we get support calls such as "The change history says I did this but I wasn't even here that day". So I decided to see what it would take to track IP Addresses.

Obviously the client knows its own IP Address and can send it to the WCF endpoints that actually do all the work. However we have a lot of endpoints and I didn't want to have to change all the contracts so I looked for a way for the end point to know the IP Address of the client that it is talking to. I turns out to be quite easy if you are targeting framework 3.5 or later.

Imports System.ServiceModel
Dim context As OperationContext
Dim prop As Channels.MessageProperties
Dim endpoint As Channels.RemoteEndpointMessageProperty

context = OperationContext.Current
prop = context.IncomingMessageProperties
endpoint = CType(prop(Channels.RemoteEndpointMessageProperty.Name), Channels.RemoteEndpointMessageProperty)

Return endpoint.Address

So this works great, but IP Addresses can change over time as they are released and renewed. Even better would be the client name, but that requires a reverse DNS lookup from the IP Address. Here is the completed code to get the IP Address from the conversation and perform a reverse DNS lookup. If the lookup fails the IP Address is returned instead.

Note that when the client and the WCF endpoint are on the same computer as is the case when you are debugging, the conversation doesn't even use TCP/IP so this all fails and returns "n/a".

Imports System.ServiceModel
Public Shared Function GetClientName() As String

    Dim context As OperationContext
    Dim prop As Channels.MessageProperties
    Dim endpoint As Channels.RemoteEndpointMessageProperty
    Dim addr As Net.IPAddress
    Dim entry As Net.IPHostEntry

    Try
        context = OperationContext.Current
        prop = context.IncomingMessageProperties
        endpoint = CType(prop(Channels.RemoteEndpointMessageProperty.Name), Channels.RemoteEndpointMessageProperty)

        Try
            addr = Net.IPAddress.Parse(endpoint.Address)
            entry = Net.Dns.GetHostEntry(addr)
            Return entry.HostName
        Catch ex As Exception
            Return endpoint.Address
        End Try
    Catch ex As Exception
        Return "n/a"
    End Try
End Function

Wednesday, January 13, 2016

DataGrid SelectedItems collection

I discovered something unexpected while researching a user's complaint today. It make sense, which may explain why it was unexpected.

The user was complaining that when they select several rows in a grid, I wasn't processing them in document number sequence, even though the grid was sorted in document number sequence. Of course, being a typical user, they omitted to mention that they weren't selecting the first row to be processed and then shift-clicking the last row. If you do that (which I always do because I think LOGICALLY) everything works as they expected.

Instead, they were selecting rows randomly. It turns out the the datagrid's selecteditems collection lists the rows in the sequence they were selected, not in the sequence they were displayed. This makes perfect sense now that I think about it.

It actually means that an intelligent user could specify the sequence they want stuff processed in. However, I doubt many developers have the luxury of a user group that is intelligent enough to use such a feature. I know I don't.

I emailed the user back saying they are using the grid selection feature incorrectly and they have to select the first row and shift-click the last row. That shut them up for a while.

One more thing, for the Microsoft people (I'm sure Bill reads my blog religiously). We seriously need a datagrid footer feature. It would be just like the header feature but at the bottom. The hacks I'm seeing online to fake up this feature are ugly.

TTFN