Friday, April 20, 2012

WPF ribbonwindow - doubleclicking application icon causes Shutdown

A WPF application whose main window is created from WPF RibbonWindow shows different behavior between closing right-top close (X) button and doubleclicking application icon on the left-top side.

When clicking right-topmost Close button, the window is closed and developer can cancel window close in window's Closing event handler by settting e.Cancel to true. Here is a callstack example when the Close button is clicked. Alt-F4 has the same effect as clicking Close button.

  Console.MainWindow.WindowConsole_Closing(object sender, System.ComponentModel.CancelEventArgs e) Line 679 C#
  PresentationFramework.dll!System.Windows.Window.OnClosing(System.ComponentModel.CancelEventArgs e) + 0x8c bytes 
  PresentationFramework.dll!System.Windows.Window.WmClose() + 0x96 bytes 
  .............
  WindowsBase.dll!System.Windows.Threading.Dispatcher.PushFrameImpl(System.Windows.Threading.DispatcherFrame frame) + 0xc1 bytes 
  WindowsBase.dll!System.Windows.Threading.Dispatcher.PushFrame(System.Windows.Threading.DispatcherFrame frame) + 0x49 bytes 
  WindowsBase.dll!System.Windows.Threading.Dispatcher.Run() + 0x4c bytes 
  PresentationFramework.dll!System.Windows.Application.RunDispatcher(object ignore) + 0x17 bytes 
  PresentationFramework.dll!System.Windows.Application.RunInternal(System.Windows.Window window) + 0x6f bytes 
  PresentationFramework.dll!System.Windows.Application.Run(System.Windows.Window window) + 0x26 bytes 
  PresentationFramework.dll!System.Windows.Application.Run() + 0x1b bytes 
  Mitutoyo.QCCAT.Console.exe!Mitutoyo.QCCAT.Console.App.Main() + 0x5e bytes Unknown
  

If a user doubleclicks leftmost application icon, the application initiates app shutdown and send window close signals to all windows. But, if window close event handler cancels, it will not be honored and simply ignored and keep going for shutdown. This means develoepr cannot cancel window close, no matter what. Here is the call stack when app icon is doubleclicked. You can see Application.DoShutdown() in the middle.

> Console.MainWindow.WindowConsole_Closing(object sender, System.ComponentModel.CancelEventArgs e) Line 679 C#
  PresentationFramework.dll!System.Windows.Window.OnClosing(System.ComponentModel.CancelEventArgs e) + 0x8c bytes 
  PresentationFramework.dll!System.Windows.Window.WmClose() + 0x96 bytes 
  PresentationFramework.dll!System.Windows.Window.InternalClose(bool shutdown, bool ignoreCancel) + 0xa1 bytes 
  PresentationFramework.dll!System.Windows.Application.DoShutdown() + 0x1b6 bytes 
  PresentationFramework.dll!System.Windows.Application.ShutdownImpl() + 0x1c bytes 
  PresentationFramework.dll!System.Windows.Application.ShutdownCallback(object arg) + 0x5 bytes 
  ...........
  [Managed to Native Transition] 
  WindowsBase.dll!System.Windows.Threading.Dispatcher.PushFrameImpl(System.Windows.Threading.DispatcherFrame frame) + 0xc1 bytes 
  WindowsBase.dll!System.Windows.Threading.Dispatcher.PushFrame(System.Windows.Threading.DispatcherFrame frame) + 0x49 bytes 
  WindowsBase.dll!System.Windows.Threading.Dispatcher.Run() + 0x4c bytes 
  PresentationFramework.dll!System.Windows.Application.RunDispatcher(object ignore) + 0x17 bytes 
  PresentationFramework.dll!System.Windows.Application.RunInternal(System.Windows.Window window) + 0x6f bytes 
  PresentationFramework.dll!System.Windows.Application.Run(System.Windows.Window window) + 0x26 bytes 
  PresentationFramework.dll!System.Windows.Application.Run() + 0x1b bytes 
  Mitutoyo.QCCAT.Console.exe!Mitutoyo.QCCAT.Console.App.Main() + 0x5e bytes Unknown
  

One can work around this problem by overwriting Applications.Close behavior. Here is a code snippet.

 public partial class MainWindow : RibbonWindow
 {
    public MainWindow()
    {
       InitializeComponent();
       this.CommandBindings.Add(new CommandBinding(ApplicationCommands.Close, ApplicationCloseExecuted)); 
    }
          
    private void ApplicationCloseExecuted(object sender,
             ExecutedRoutedEventArgs e)
    {
       this.Close();
    }

    private void Window_Closing(object sender, CancelEventArgs e)
    {
       if (MsgBox("Sure?") == true) {
          Terminate();
       }
       else {
          e.Cancel = true; // cancel window close
       }
    }
 }

Thursday, April 12, 2012

LINQ to Entities : this method cannot be translated into a store expression

In LINQ to Entities or LINQ to SQL, if C# user-defined function is used in query expression, this function cannot be translated to SQL expression so the following error can occur.

{"LINQ to Entities does not recognize the method 'System.Collections.Generic.IList`1[System.String] getEmp(Int32)' method, and this method cannot be translated into a store expression."}

Here is an example of calling C# function directly from LINQ query. This will throw an exception since getEmp cannot be converted to a pure SQL statement.

private void WrongRun()
{
    MyDBEntities db = new MyDBEntities();
    var p = from d in db.Depts
            where d.ID == 1
            from emp in getEmp(d.ID)
            select new { d.Name, emp };

    foreach (var x in p)
    {
        Debug.WriteLine(x.Name + ": " + x.emp);
    }
}

public IList getEmp(int deptid)
{
    return new EmpData().GetEmps(deptid);            
}

Assuming there is primary key (Dept.ID) / foreign key (Emp.DeptID) relationship between Dept and Emp table, the problem can go away by rewriting LINQ query as follows.

private void Run()
{
    MyDBEntities db = new MyDBEntities();
    var p = from d in db.Depts
            where d.ID == 1
            from emp in d.Emps
            select new { d.Name, emp.EmpID };

    foreach (var x in p)
    {
        Debug.WriteLine(x.Name + ": " + x.EmpID);
    }
}

The relationship between Dept and Emp is 1 to many, so SelectMany() expression will be used if it is rewritten in LINQ methods.

Thursday, April 5, 2012

InstallShield : Check running process or get process list (InstallScript)

InstallShield does not provide any native function for checking processes, which led users to use Win32 API or WMI provider if they want to check a process or get process list. I found a 'List and Shut Down Running Applications' code in http://www.installsite.org but the code was not working in Windows 7 64bit machine. I didn't want to spend too much time on it, so moved on and tried WMI approach which worked for me.

Check to see if specific process is running.
IsAppRunning() function checks to see if a specified process is currently running on the machine. Basically the code finds a process in Win32_Process WMI class.

function BOOL IsAppRunning(appName)      
 OBJECT wmi, procs;      
begin         
  try
    set wmi = CoGetObject( "winmgmts://./root/cimv2", "" ); 
    if ( !IsObject(wmi) ) then   
        return FALSE;
    endif;
    set procs = wmi.ExecQuery ("Select * from Win32_Process Where Name = '" + appName + "'" ); 
    if (!IsObject(procs)) then    
 return FALSE;
    endif;          
                
    if (procs.Count > 0) then
       return TRUE;
    endif;
  catch  
  endcatch;
  return FALSE;
end;

Get running processes
Another similar code is about getting all processes currently running on the machine. This task also can be done by referring to Win32_Process class but it requires to enumerate all data from the result. This looping task is tricky and cannot be done by simply using for loop. Again, http://www.installsite.org site provides 'Get Object and ForEach in InstallScript' code and it worked great for me.

(1)Download GetObject.zip from installsite.org

(2)Unzip it and copy IsGetObj.dll to InstallShield (2010/mine) - Behavior and Login - Support Files / Billboards - Language Independent. This will copy the DLL file to SUPPORTDIR directory which is only available during installation. SUPPORTDIR is temp folder typically created under current user folder.

(3) Add prototype at the top of the script
   prototype ISGetObj.ForEachStart(byref OBJECT, byref VARIANT);
   prototype ISGetObj.ForEachGetNextItem(byref VARIANT, byref OBJECT);
(4) Use ForEachStart and ForEachGetNextItem function to iterate item from WMI query resultset.

function STRING GetProcesses()      
  OBJECT wmi, procs, procItem;      
  VARIANT __varEnumHolder;  
  STRING procList;
begin         
  try
   set wmi = CoGetObject( "winmgmts://./root/cimv2", "" ); 
   if ( !IsObject(wmi) ) then   
      return "";
   endif;
   set procs = wmi.ExecQuery("Select * from Win32_Process"); 
   if (!IsObject(procs)) then    
      return "";
   endif;          
                
   UseDLL(SUPPORTDIR ^ "IsGetObj.dll");   
   ForEachStart(procs, __varEnumHolder);    
   while(0 == ForEachGetNextItem(__varEnumHolder, procItem))  
     procList = procList + "\n" + procItem.Name;
   endwhile;       
   
   set __varEnumHolder = NOTHING;
   UnUseDLL("IsGetObj.dll");
  catch  
  endcatch;
  return procList;
end;   

The example above returns all running process names separated by CR.

Wednesday, April 4, 2012

Display empty string in DateTimePicker

By default, WinForms DateTimePicker control displays current date. Depending on Format property of the DateTimePicker control, it can display long date and time(Format=Long), short date(Format=Short) or time only(Format=Time). And if the Format is set to Custom, various user defined format is possible.

When it comes to DateTimePicker, some people prefers to display empty string instead of current date/time when the form is first shown up. And the thing is DateTimePicker control does not allow you to enter empty string easily, since Value property of the control is DateTime type which does not allow null value. If it were DateTime? nullable type, displaying empty string would have been an easy task. Some developers make Nullable DateTimePicker control by inheriting it, but a workaround can be used by utilizing custom format.
public partial class Form1 : Form
{
    public Form1()
    {
        InitializeComponent();
    }
    private void Form1_Load(object sender, EventArgs e)
    {
        // Setting CustomFormat to a Space.            
        dateTimePicker1.Format = DateTimePickerFormat.Custom;
        dateTimePicker1.CustomFormat = " ";            
    }
    private void dateTimePicker1_ValueChanged(object sender, EventArgs e)
    {
        // Change Format to Long,Short or Time
        dateTimePicker1.Format = DateTimePickerFormat.Short;
        // add more
    }
}

To add empty string to DateTimePicker textbox, set Format to Custom and then assign empty string to CustomFormat property in form Load event handler or form constructor. Once user starts changing the DateTimePicker value, we reset the Format back to normal format such as Long, Short, Time.