Monday, March 4, 2013

WPF and MessageBox in Dispatcher.Invoke

Painful, painful, painful.

That was my experience when a WPF application I was creating would display a MessageBox on startup.  For efficiency many things were going on simultaneously, and most was tripped off with Dispatcher.BeginInvoke.  If something needed user interaction at startup, a MessageBox would display to get the user's attention.  The problem was, it was not modal, so the user would see the MessageBox flash and disappear, with the default value selected (which was very problematic).

A google search finally revealed the answer.  When using Dispatcher.Invoke, you need to pass the owning window to MessageBox:

MessageBox.Show(this, "message");

Friday, February 1, 2013

Helper for System.Diagnostics.Process

It's one thing to kick off a process from C# using Process.Start out of System.Diagnostics.  Nothing to that.  But what if you want to send the process some input beyond the arguments?  Or you want to perform processing on the output is sends to the console?



This code provides that help.

Just call ProcessHelper.Start.

The delegates for output and error provide a mechanism for processing each line of output.  The delegate will be called for each line produced.


This code below provides a simple example of how to use the ProcessHelper.  Each line of output is simply added to the StringBuilder objects.




StringBuilder outData = new StringBuilder();
StringBuilder errData = new StringBuilder();

System.Diagnostics.Process p = ProcessHelper.Start("MyProcess.exe", "arg1 arg2 arg3", "Standard Input Data",
        delegate(string parm)
        {
            outData.AppendLine(parm);
        },
        delegate(string parm)
        {
            errData.AppendLine(parm);
        }
);

p.WaitForExit();

Friday, May 18, 2012

ClickOnce Hell

This post is just a comment on my basic impression of ClickOnce, after having lived through developing for it for several years.

If your application is relatively small and simple, ClickOnce is great.  Basically, one (maybe two or three) project in the solution, compile size relatively small (to handle slow connections if necessary).

The problems start to arise when your solution starts to get complex.  Multiple projects, with references in XAML crossing the projects, and it gets a bit tricky.  Stuff that works fine in Visual Studio tends to not work once deployed through ClickOnce, unless you are very careful in your reference definitions.  It only takes one misdefined definition, and the whole thing can break--and the error messages received will likely be too generic to easily find it.

So with a complex application, ClickOnce becomes completely unfeasible.  In my opinion, it's a piece of crap, unless you keep your application small and simple--then it's great.

I use it on my small and simple applications as it offers easy deployment.

But on the one complex application I support, originally it was deployed as ClickOnce, but at some point the ClickOnce deployment broke, probably due to an incorrectly defined reference.  The application worked fine in Visual Studio, or when installed using WiX.  But it would go so far in ClickOnce, then blow up, and I never could figure out what ClickOnce didn't like.

So it was easier to give up on ClickOnce and switch to installing with WiX and use our own home-grown mechanism for updating the application.  That home-grown mechanism had its own set of problems, but at least it was under our control.

As a final note: WiX is great.  Being driven by XML allowed automatic update of the project definition through Team Systems Build.  If  I get around to it, I might post the recipe for this.  Leave a comment if this recipe would be valuable to you (to encourage me to post it sooner).

Assembly Unloading

I needed a simple way of checking version information of an assembly that was not loaded into memory.  On System.Reflection.Assembly, there is the Load method, but unfortunately, now the assembly is in memory with no way of unloading it--eating up precious RAM.

However, .NET offers the AppDomain.  Simply create a new AppDomain and load assemblies into that for reviewing information of the assembly, then unload the domain.

Example:


System.AppDomain newdomain = System.AppDomain.CreateDomain("DataVerification");
Assembly assm = newdomain.Load("MyAssembly.dll");
MessageBox.Show("Assembly version: " + assm.GetName().Version.ToString();
System.AppDomain.Unload(newdomain);

Tuesday, April 17, 2012

Perceived Types


Never could figure this one out complete.  We have an application that looks in the ClassesRoot Registry for the extension of a file, looking for the "PerceivedType" entry, and rendering an image if the PerceivedType is "image".  The problem for this particular computer was that its initial configuration was bad, and it had no extensions configured to image.  No idea why, nor how to automatically fix it or prevent it going forward.

Simple solution was to create a Registry merge file that included all extensions and added the "PerceivedType"="image" to all of them.  However, I stumbled onto a Windows Shell API call that would first check some hard-coded extensions for their perceived type, then check the registry, so there was potential that if our application used this call, AssocGetPerceivedType, the problem may not have surfaced in the first place.

But I could not find an example of the call defined in C# anywhere.  What a pain!  You mean no one has written C# that needs this call???????

I found C declaration for the call:

HRESULT AssocGetPerceivedType(
  __in       PCWSTR pszExt,
  __out      PERCEIVED *ptype,
  __out      PERCEIVEDFLAG *pflag,
  __out_opt  PWSTR *ppszType
    );

Now the question was, how to get it to C#?


It wasn't too hard, but did take some digging.  Below is the full code I used to get the perceived type of a file.  Simply check the returned value from the GetPerceivedType function below, passing the extension:






        [DllImport("Shlwapi", EntryPoint = "AssocGetPerceivedType")]
        private static extern int AssocGetPerceivedType(IntPtr extension, out IntPtr PERCEIVED, out IntPtr flag, out IntPtr PerceivedType);
        public string GetPerceivedType(string extension)
        {
            IntPtr result = IntPtr.Zero;
            IntPtr dummy = IntPtr.Zero;
            IntPtr intPtr_aux = Marshal.StringToHGlobalUni(extension);
            AssocGetPerceivedType(intPtr_aux, out dummy, out dummy, out result);
            string s = Marshal.PtrToStringAuto(result);
            return s;
        }
        

Tuesday, January 31, 2012

Axiom of Truth


If it takes two contractors six months to drive a project to failure, four contractors will achieve that failure in half the time, but it will take twice as long for the failure to be acknowledged.

Tuesday, January 10, 2012

Filter for all supported Image file types

Nifty little trick: for the OpenFileDialog, I want to set a filter for all Image file types supported by the PC, including ones that haven’t been invented yet. My code will load it into some sort of Image control for displaying. I could manually set my filter to something like “*.jpg;*.png”, etc., but then I’d have to update it whenever a new format was introduced, and some PCs may support types not supported by others, due to what has been installed on the PC.

This code below solves this problem.  It uses the MIME types that are configured for the PC, as defined in the Windows Registry.  Nice and simple solution.




  public static string GetImageFormatsFileFilter()
        {
            string retVal= "*" + string.Join(";*", SupportedImageFormats());
            return retVal;
        }
        public static string[] SupportedImageFormats()
        {
            List retVal = new List();
            RegistryKey BaseKey = Registry.ClassesRoot.OpenSubKey("MIME").OpenSubKey("Database").OpenSubKey("Content Type");
            foreach (string subkey in BaseKey.GetSubKeyNames())
            {
                if (subkey.ToUpperInvariant().StartsWith("IMAGE/"))
                {
                    string ext = (string)BaseKey.OpenSubKey(subkey).GetValue("Extension");
                    if (ext != null && !retVal.Contains(ext.ToUpperInvariant()))
                    {
                        retVal.Add(ext.ToUpperInvariant());
                    }
                }
            }
            return retVal.ToArray();
        }

Thursday, August 25, 2011

ListView column sorting

Found plenty of samples for WPF using a ListView and sorting on a column when the column header is clicked.
But I could not get any of them to work!!!
They never mentioned that this doesn't work with BindingList!!!
Switched to ObservableCollection<t>, and it worked fine.
I'm going to stop using that BindingList.

Russ

Wednesday, December 1, 2010

XAML and cryptic error message

Got the following cryptic error message when running a WPF application (a XamlParseException):
A 'Binding' cannot be used within a '<usercontrol>' collection. A 'Binding' can only be set on a DependencyProperty of a DependencyObject.
At first glance, everything looked good.  It might have been helpful if the error told me which property it was croaking on--but I guess MS can't learn to be specific with this XAML crap.  Fortunately, a quick google search and I stumbled onto a post where they identified thier problem when they got the same error: the owner in the DependencyProperty definition was not the control the property was on--changing it fixed their problem.  A second look at my code and I found I had done the same thing (the problem with cut-and-pasting).  Changing the owner type to the correct class fixed the problem.

Monday, November 29, 2010

Useless SQL Server Errors

Okay, there's nothing more useless than getting an error that doesn't tell you anything about how to fix it or, at the very least, why it occurred.
I had a Transaction Log backup job that was running regularly on SQL Server 2000, and was regularly getting failure, with nothing more than "SQLSTATE 42000, Error 22029".   Googling was not very helpful--seemed this could be caused by a who slew of possibilities, none of which appeared to apply to me.
It wasn't until I opened the text log file that was produced from the job and googled on the specific message ("Backup can not be performed on database <database>. This sub task is ignored.") that I found my answer, at http://support.microsoft.com/kb/303229.  It would have been nice to have something basic in the error message that told me the problem--it would have saved me a few hours of digging.
Basically, the Recovery Model for this database was "Simple".  Apparently you can't back up the transaction log on databases defined as Simple Recovery Model.  So there are two simple choices:  1. Change the Recovery Model to something other than "Simple" ("Bulk-Logged" or "Full" are both valid for backing up the transaction log), or 2. Don't back up the transaction log.
Since, in my case, the database is replicated to another server, it seemed rather redundant to back up the transaction log.  So, I turned off this job altogether.  Problem solved.