Thursday, December 17, 2009

Detecting Design Mode in WPF controls

Who would've thought that it would be difficult to find information on detecting Design mode in WPF controls.  In Winforms, there is a simple DesignMode boolean property you can check, but it's not so obvious in WPF, plus Bing searches gave convoluted answers (you know, the kind of answer you get from a know-it-all geek), although the answer was there.
 
So here's the beef:
 
System.ComponentModel.DesignerProperties.GetIsInDesignMode(this);
 
This returns a boolean, and "this" is your UserControl or something that is a DependencyObject.

Tuesday, December 8, 2009

Updating WPF window from background thread

I like to do things in the background--you know, you've got a long-running process, so you don't want to bother the user while it runs.

But you want to notify the user when the process is done.  Trouble is, since the background process is running in a different thread from the user interface, the process crashes if you make any attempt to update the user interface directly.

Here's some sample code to solve this with the Dispatcher--kind of well known, but nice to document anyhow:


void UpdateText(string text)
{
    if (this.Dispatcher != System.Windows.Threading.Dispatcher.CurrentDispatcher)
    {
        this.Dispatcher.Invoke(new Action(Update), text);
    }
    else
    {
        this.sampleTextBox.Text = text;
    }

}

Monday, December 7, 2009

C# Inheritance, Typecasting, and Reflection

Did you know that an inherited class cannot be cast into its base class so that reflection will think it's working with the base class?



This proved a minor nuisance as the code had a simplistic work-around, but this might not always be the case.



Below is an example of what I'm talking about:


using System.Reflection;
public static class UtilityClass
{
    public static object GetResult(object workClass)
    {
        string ResultingClassName = "Result" + workClass.GetType().ToString();
        Type provider = Type.GetType(ClassName, true);
        ConstructorInfo constructor = provider.GetConstructor(new Type[0]);
        return constructor.Invoke(new object[0]);
    }
}


public class ResultClass
{
    public ResultClass()
    {
    }
}
public class Class
{
    public ResultClass GetResult()
    {
        return (ResultClass)UtilityClass.GetResult(this);
    }
}
public class ChildClass : Class
{
    public void DoTest()
    {
        ResultClass x = ((Class)this).GetResult();
    }
}

The above code will fail with an exception when DoTest() in ChildClass is called, because it will try to create ResultChildClass, which does not exist.  No matter how you cast, you can't get this to work, because the GetResult method of UtilityClass, using Reflection, will always identify the object for what it really is.

In my case, the solution was to simply create a new "Class" object and copy the base of the ChildClass object into it, then call the "GetResult" method to get my "ResultClass".

Friday, December 4, 2009

System.OutOfMemoryException from Visual Studio

Lately, with my WPF project, I commonly get OutOfMemoryException whey I try to run my project. Visual Studio builds the project, then gets the exception when it tries to start up the executable.

I found a decent work-around is to Build the solution first. Then run the solution. This two-step process seems to solve the issue in that when you run it, Visual Studio doesn't build it--so I guess it's got time to clean up memory before starting your executable.

You might eventually still run out of memory, but this'll give you some extra time before you have to close Visual Studio and start it up again.

Thursday, December 3, 2009

XAML Binding to Datasets

Found that DataSets make lousy binds for WPF forms when the data must be updated from something other than directly by the user, because datasets do not implement INotifyPropertyChanged.

Datasets are great when the data is OneWay binding, because heirarchical data relationships are so easy to define. But you've got to get creative and use something else, such as a BindingList<> when the binding must be TwoWay or OneWayToSource.

Wednesday, December 2, 2009

XAML and the loss of Intellisense

Last week I reported losing my intellisense and tag completion on XAML pages.
 
I finally figured out the cause--and it's not good.  I happend to start up a new solution with a single page and all of a sudden I noticed I had my Intellisense and tag completion back!
 
After getting all excited about it, I went back to my original solution that was having problems--Intellisense and code completion disappeared.
 
Yet they came back with the nothing of a solution.
 
Okay--I get the hint.  XAML and WPF take lots and lots of RAM.  I have only 2GB.  From what I'm gathering, you need at least 3GB RAM.
 
All this only after I totally hosed up my Visual Studio trying to figure it all out.
 

Tuesday, December 1, 2009

Using Syncfusion

I'll give the support team at Syncfusion credit--they do seem to respond quickly to help you out.
 
But I'd give the development team there a failing grade.  They seem to be prone to deploy rather buggy controls for WPF use.
 
Seems getting "NullReferenceException" is commonplace with their controls.  And I just stumbled onto a stupid one--using their DateTimeEdit control and setting the MinDateTime attribute causes the control to display "01/01/0001" as its datetime value.  These kind of bugs are extremely annoying when you are trying to get a job done.  They're so bad that I don't know how they get away with charging for them when so many open-source and non-buggy alternatives are available.  Had I been give the ability to choose which controls to use, Syncfusion would never have been considered.
 
Fortunately, I stumbled onto http://wpf.codeplex.com/Release/ProjectReleases.aspx?ReleaseId=29117.  There is a DatePicker control there that'll work for me--and there's probably a large number of other controls.  Especially since Syncfusion is about as crappy as it comes--try finding what you need without asking their support department--I'm going to trash the Syncfusion control usage wherever possible.
 

Friday, November 27, 2009

Syncfusion GridDataControl and referencing other columns

I was using the Syncfusion GridDataControl in WPF and needed to reference a different column from within the column I was in, so that different attributes could be set based on other columns.  Found out from the SyncFusion Form support center that it's pretty easy.  Just set the Binding Path as follows:
 
Binding Path=Record.Data.OtherColumnName

WPF form and debugging data

I never realized just how painful it can be to debug data that should (or should not) display on a XAML form.  I bound a XAML form to a DataSet.  There was a problem with one of the columns in the dataset that caused problems with the XAML form.  But since there is no way to step through the debugger on the XAML, there was no way to figure out what the specific data was that was giving me problems.
 
The problem was solved through a Converter.  I discovered you can put a breakpoint on a converter, then view your data.  In my case, I had no need of a converter, so couldn't set up a breakpoint.
 
The solution was to add a special converter.  This one did not do any actual conversion--it simply returned the passed value.  But it gave me a simple way to put a breakpoint.
 
Below is the converter code:

class LogObjectDataConverter : IValueConverter
{
#region IValueConverter Members
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
   
return value;
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
return value;
}

Thursday, November 19, 2009

0x8007001f from ClickOnce install/upgrade

 This has got to be the most painful thing to come across on a ClickOnce install.  Error messages are almost meaningless and Google searches produce nothing useful.  I'm documenting my torment in finding a solution in the event someone else comes across the same thing.
 
The problem was first encountered when we updated one third-party dll.  The error produced during install or upgrade was :
 
System.Deployment.Application.InvalidDeploymentException (ManifestLoad)
  - Exception occurred loading manifest from file <dllfilename>.dll: the manifest may not be valid or the file could not be opened.
 
Further down the log was:
 
 
System.Deployment.Application.InvalidDeploymentException (ManifestParse)
  - Parsing and DOM creation of the manifest resulted in error. Following parsing errors were noticed:
 
 
And finally:
 
 
System.Runtime.InteropServices.COMException
  - A device attached to the system is not functioning. (Exception from HRESULT: 0x8007001F)
 
I sent an email to the third-party vendor's support department for help, and they gave me a checklist of things to try.  Unfortunately, they were of the "if all else fails, blow it away and restart from scratch" nature--if the pc had other ClickOnce applications installed, they would also be blown away in following their instructions, and neither I nor they weren't even sure any of their suggestions would work.  So I tried their suggestions in a much more controlled and refined approach--choosing to be more surgical in fixing the issues.  I used a PC that had the old version of the application installed at one point that I didn't care about any other Click Once application it had (just in case).
 
Their tips:
1. Try uninstalling and reinstalling.  No good--I already had the application uninstalled to start with.  bogus tip.
2. Try running:   mage -cc.
 
This clears the Application Cache.  Sounds promising, but no good--same problem.
 
3. Delete the ClickOnce application store. 
 
This just sounds bad--I'm certain it'll blow away all Click Once applications.  I just want to clear out one ClickOnce application.  Better yet--just clear out any references to the DLL I'm updating.  Well, they gave these steps:
 
    a. Make sure DFSVC.EXE process is not running.  Check.  It was running.  They say to killing out right (from Task manager).  Bummer--this might mean a reboot when done, unless I can find the Services to run to restart it.
    b. Delete the application store folder %userprofile%\Local Settings\Apps.  Okay, this is where I'm going to try to be a bit surgical.  If being surgical fails, I'll use the nukes.  I found the folders that contained copies of my old version dll and made sure the folder was for my application (even though the application was not installed), deleting the entire folder (it's not installed anyhow, so it shouldn't matter).  That failed because one of the files was in use.  I was able to delete the old-version dll I cared about, so that was good enough.
    c. Delete folder %userprofile%\Local Settings\Apps.  I don't know, but that sounds a lot like "b" above.  okay--I'm moving on.
    d. Delete HKCU\Software\Classes\Software\Microsoft\Windows\CurrentVersion\Deployment.  That's Regedit, and looking into it, there's a lot there.  Being surgical, I went further down to \SideBySide\2.0\Components\ and found the folder for my DLL, deleting the whole folder.
 
I did all this and tried to install again--dfsvc did start on its own so a reboot wasn't necessary--but the install still failed.  Okay, let's try bigger guns.  I searched the registry for my dll underneath the Deployment key and found a few other copies (after stopping that DFSVC process).  Same  thing.  Okay--time for the "deployment" key to be deleted.  Also removing the "Apps" folder.
 
NOTHING WORKS!!!!
 
Finally gave up entirely.  We determined that the third-party vendor had to be installed in the GAC anyhow to work--so we just removed the file from the project and made sure the reference was set up to use the GAC version.  Problem solved.
 
I spent most of the day Googling and Binging for help on this--even Microsoft either didn't respond with answers on the few forums that encountered the problem, or if Microsoft did respond it was with an answer that didn't make sense--or didn't explain how you would accomplish the recommended task.
 
I just hope I never see this one again.  But if I do--I hope the answer gets found.

 

Wednesday, October 28, 2009

Why Team Build won't customize app.config

I was bashing my head over app.config not getting customized upon deployment, even though I confirmed values changed in app.config before the compile step.
 
Then it occurred to me that I was using user settings, and that a Settings.settings file was being generated.  It looks like this modifiying this file for default values is actually more important that modifying the app.config file, depending on how you are doing your settings.
 

Wednesday, September 30, 2009

WIA in C# Redux: for Vista!

Of all the insanity! The scanning code I posted earlier does not work with the default configuration of Vista! Vista does not have the wiascr.dll file. However, it does have wiaaut.dll. Code I found at http://www.eggheadcafe.com/community/aspnet/2/76650/heres-some-sample-code.aspx finally gave me the answer after bashing my head around. It was easy to find code that worked for a WebCam, but nearly impossible to find code that worked for the scanner I was using. I guess no one out there uses scanners. Anyway, here's the modified code. Note that I added a ScannerException class--this just inherits the Exception class--nothing special about it:


///
/// This is a simple wrapper around WIA.
///

public class Scanner
{
public class AcquireEventArgs : EventArgs
{
public Image Image { get; internal set; }
}
WIA.DeviceManager manager = new WIA.DeviceManagerClass();

///
/// Acquires the images.
///

public void AcquireImages()
{
WIA.CommonDialogClass diag = new WIA.CommonDialogClass();
System.Object Object1 = null;
System.Object Object2 = null;
WIA.Device dev = null;
try
{
dev = diag.ShowSelectDevice(WIA.WiaDeviceType.UnspecifiedDeviceType, true, false);
}
catch (Exception ex)
{
if (ex.Message.EndsWith("00153.") || ex.Message.EndsWith("21006A."))
{
throw new ScannerException("Scanner Not connected", ex);
}
else
{
throw new ScannerException("Scanner problem", ex);
}

}
if (dev != null)
{


WIA.Item Item1 = ItemObjectReturnedFromInitializingScanner(ref dev);
WIA.ImageFile Image1 = new WIA.ImageFile();
WIA.ImageProcess ImageProcess1 = new WIA.ImageProcess();
Object1 = (Object)"Convert";
ImageProcess1.Filters.Add(ImageProcess1.FilterInfos.get_Item(ref Object1).FilterID, 0);
Object1 = (Object)"FormatID";
Object2 = (Object)WIA.FormatID.wiaFormatTIFF;
ImageProcess1.Filters[1].Properties.get_Item(ref Object1).set_Value(ref Object2);
Object1 = (Object)"Compression";
Object2 = (Object)"CCITT4";
ImageProcess1.Filters[1].Properties.get_Item(ref Object1).set_Value(ref Object2);
Object1 = null;
Object2 = null;


try
{
WIA.ImageFile imagefile = Item1.Transfer(WIA.FormatID.wiaFormatTIFF) as WIA.ImageFile;

if (ImageScanned != null)
{
AcquireEventArgs e = new AcquireEventArgs();
using (MemoryStream ms = new MemoryStream((byte[])imagefile.FileData.get_BinaryData()))
{
e.Image = new Bitmap(ms);
}
ImageScanned(this, e);
}
}
catch (Exception ex)
{
throw new ScannerException("Problem with Scanner", ex);
}
}
}

private WIA.Item ItemObjectReturnedFromInitializingScanner(ref WIA.Device Scanner)
{
WIA.Item Item1 = null;
Object Object1 = null;
Object Object2 = null;
Int32 DPI = 200;
foreach (WIA.Item CurrentItem in Scanner.Items) // 'Scanner settings.
{

Item1 = CurrentItem;
try
{
Object1 = (Object)"6146";
Object2 = (Object)4;
CurrentItem.Properties.get_Item(ref Object1).set_Value(ref Object2);
}
catch
{ }
try
{
Object1 = (Object)"6147";
Object2 = (Object)DPI;
CurrentItem.Properties.get_Item(ref Object1).set_Value(ref Object2);
}
catch
{ }
try
{
Object1 = (Object)"6148";
Object2 = (Object)DPI;
CurrentItem.Properties.get_Item(ref Object1).set_Value(ref Object2);
}
catch
{ }
try
{
Object1 = (Object)"6149";
Object2 = (Object)0;
CurrentItem.Properties.get_Item(ref Object1).set_Value(ref Object2);
}
catch
{ }
try
{
Object1 = (Object)"6150";
Object2 = (Object)0;
CurrentItem.Properties.get_Item(ref Object1).set_Value(ref Object2);
}
catch
{ }
try
{
Object1 = (Object)"6151";
Object2 = (Object)(8.5 * DPI);
CurrentItem.Properties.get_Item(ref Object1).set_Value(ref Object2);
}
catch
{ }
try
{
Object1 = (Object)"6152";
Object2 = (Object)(11.5 * DPI);
CurrentItem.Properties.get_Item(ref Object1).set_Value(ref Object2);
}
catch
{ }

foreach (WIA.Property Prop in Scanner.Properties)
{
if (Prop.PropertyID == 3088) //
{
try
{
Object1 = (Object)5;
Prop.set_Value(ref Object1); //'This is my effort to enforce duplex. I
}
catch (Exception) { }
}
}
}
Object1 = null;
Object2 = null;
return Item1;
}

///
/// Occurs when [image scanned].
///

public event EventHandler ImageScanned;


}

Monday, September 21, 2009

Mouse drawing in WPF

I was trying to build a simple control to allow mouse drawing via the WPF. I found one web site that seemed to make the process a bit more complicated than doing the same thing in Windows Forms--but I tried it anyhow, and I simply couldn't make it work. I don't remember the website, but it basically used a canvas, and attempted to draw shapes on it based on Mouse movements. I had implemented routines bound to MouseDown, MouseUp, and MouseMove events, but those events never seemed to execute.
Then I discovered the InkCanvas control--and that solved all my problems. That original code must have come from before the existance of the InkCanvas, or the programmer simply didn't know any better. After switching to use the InkCanvas code I could remove all event handling code so that I only needed the code to capture the resulting image and save it. Below is the XAML:

<

UserControl x:Class="RCO.Imaging.SignaturePad.MouseSignaturePad.SignaturePad"
xmlns=http://schemas.microsoft.com/winfx/2006/xaml/presentation
xmlns:x=http://schemas.microsoft.com/winfx/2006/xaml
HorizontalAlignment="Stretch" Width="Auto" Height="Auto" VerticalAlignment="Stretch" >

<InkCanvas Name="canvas1" Width="Auto" Height="Auto" VerticalAlignment="Stretch" HorizontalAlignment="Stretch" ></InkCanvas>

</

UserControl>

And here is the C# behind it:





using
System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;

namespace

MousePad
{
/// <summary>
/// Interaction logic for SignaturePad.xaml
/// </summary>
public partial class SignaturePad : UserControl
{
public SignaturePad()
{
InitializeComponent();
}

public static RenderTargetBitmap ToImageSource(FrameworkElement obj)
{

// Save current canvas transform
Transform transform = obj.LayoutTransform;
obj.LayoutTransform =
null;
// fix margin offset as well
Thickness margin = obj.Margin;
obj.Margin =
new Thickness(0, 0,
margIn.Right - margin.Left, margin.Bottom - margin.Top);
// Get the size of canvas
Size size = new Size(obj.ActualWidth, obj.ActualHeight);
// force control to Update
obj.Measure(size);
obj.Arrange(
new Rect(size));
RenderTargetBitmap bmp = new RenderTargetBitmap(
(
int)obj.ActualWidth, (int)obj.ActualHeight, 96, 96, PixelFormats.Pbgra32);
bmp.Render(obj);
// return values as they were before
obj.LayoutTransform = transform;
obj.Margin = margin;
return bmp;
}
public void AcceptImage()
{
using (System.IO.MemoryStream outStream = new System.IO.MemoryStream())
{
// Use png encoder for our data
PngBitmapEncoder encoder = new PngBitmapEncoder();
// push the rendered bitmap to it
encoder.Frames.Add(BitmapFrame.Create(ToImageSource(canvas1)));
// save the data to the stream
encoder.Save(outStream);
outStream.Position = 0;
// Do something with the MemoryStream here--save it to file or pass it back into system.
}

}

}

}

Thursday, September 17, 2009

WIA in C#

Been awhile.  Ran into a problem of needing to write code to acquire an image from a scanner.  I had done TWAIN coding, but I've found that TWAIN is now gone by the wayside--in otherwords, don't use it.
 
Instead, use WIA.  I had found Microsoft's WIA SDK, but discovered that its examples are in VB6, to give you an idea how old the stuff is.  And on top of it--it seemed a bit convoluted.
 
Then I ran into http://www.codeproject.com/KB/dotnet/wiascriptingdotnet.aspx, which had a good example, though a little on the incomplete side (he left off the variable declaration statements).  It seemed too simple, but I tried it out. 
 
And wouldn't you know it--that was all that was needed!  The link also include video information, but that was beyond the scope of my project.
 
Below is my code which wraps the WIA (strongly based on the above link).  All you need to do is create the Scanner object, assign the "ImageScanned" event, the call the AcquireImages method.  This code works asynchronously, so you can have other things going on while you wait for the images:
 

using System;
using System.Collections.Generic;
using System.Drawing;
using System.IO;
using System.Runtime.InteropServices;
using WIALib;
namespace WIAWrapper
{
    /// <summary>
   
/// This is a simple wrapper around WIA.
   
/// </summary>
    public class Scanner
    {
        public class AcquireEventArgs : EventArgs
       
{
           
public Image Image { get; internal set; }
        }
        object selectUsingUI = System.Reflection.Missing.Value;
        ItemClass wiaRoot;
        WIALib.
WiaClass wiaManager;
        public Scanner()
        {
            wiaManager =
new WIALib.WiaClass();
            wiaRoot = (
ItemClass)wiaManager.Create(ref selectUsingUI);
        }
        /// <summary>
       
/// Acquires the images.
       
/// </summary>
        public void AcquireImages()
        {
            List<Image> retVal = new List<Image>();
            CollectionClass wiaPics = wiaRoot.GetItemsFromUI(WiaFlag.SingleImage, WiaIntent.ImageTypeColor) as CollectionClass;
            wiaManager.OnTransferComplete +=
new _IWiaEvents_OnTransferCompleteEventHandler(wiaManager_OnTransferComplete);
            foreach (object wiaObj in wiaPics)
            {
                ItemClass wiaItem = (ItemClass)Marshal.CreateWrapperOfType(wiaObj, typeof(ItemClass));
                string imgFile = Path.GetTempFileName();
                wiaItem.Transfer(imgFile,
true);
            }
        }

        public event EventHandler<AcquireEventArgs> ImageScanned;

        /// <summary>
       
/// Wias the manager_ on transfer complete.
       
/// </summary>
       
/// <param name="Item">The item.</param>
       
/// <param name="Path">The path.</param>
        void wiaManager_OnTransferComplete(Item Item, string Path)
       
{
            if (ImageScanned != null)
            {
                AcquireEventArgs e = new AcquireEventArgs();
                e.Image =
new Bitmap(Path);
                ImageScanned(
this, e);
            }
        }
    }
}

Wednesday, July 15, 2009

Using XPath to modify app.config on setup

Sample code to modify the app.config when executing a setup package:

This assumes it is in the "Committed" event of a projectinstaller class. This sample only modifies a connectionString, but can easily be used for anything:

string assemblypath = Context.Parameters["assemblypath"];
string appConfigPath = assemblyPath + ".config";
XmlDocument doc = new XmlDocument();
doc.Load(appConfigPath);
XmlNode aConnection = doc.SelectSingleNode("/configuration/connectionStrings/add[@name=\"aConnectionIdentifier\"]");
aConnection.Attributes["connectionString"].Value = "Data Source=.;Initial Catalog=MyDatabase;User Id=MyUser;Password=MyPassword;";
doc.Save(appConfigPath);


Wednesday, July 8, 2009

Embedding your DLLs into your EXE

Good info at http://dotnetzip.codeplex.com/Wiki/View.aspx?title=Embed%20DotNetZip&referringTitle=Home for embedding your DLLs into your EXE, instead of having them separate. Not sure why you might want to do this, other than to obfusicate your code a bit, but you never know.

Great .NET Zip library

http://dotnetzip.codeplex.com is a super library for zip functionality in .NET. I had problems viewing the compiled help file that you can download, but there are so many examples on the website, it really isn't necessary. It offers functionality equivalent to what you can get with WinZip 9.0 (AES encryption, too).

Wednesday, July 1, 2009

ClickOnce and Team Build

Okay, I've figured it all out. First off, there are too many "Version" terms. There's the AssemblyVersion, the version the user sees on the landing page, the BuildNumber, which is commonly part of the "Version", and finally, the one thing my brain was not wrapping itself around, the "PublishVersion". This publish version is not visible to the end-user, but it's stored in the manifest and is key to letting the ClickOnce application know that an update is available. I was wanting to override it, but I wasn't fully grasping that it was really its own thing--separate from AssemblyVersion.

What I wanted was a PublishVersion that came from the Publish version on the properties page of the project, but used the ChangeSet in place of the Revision number. I didn't care if the Revision was updated in Source Control since it would always be ignored anyhow and overridden on each build.

Everything I found went through all sorts of hoops to basically accomplish this. Apparently MSBuild doesn't offer a simple way to pass this as a property on the task, so for awhile it appeared the only solution was to use MAGE somehow--but I wanted to avoid that because ther seemed to be such an overly-complicated set of configuration for it--it would also have to be used to generate the manifest

Then it occurred to me--the light bulb upstairs finally came on--why not just update the csproj file using a XMLModify to replace the Revision for the Publish version with the changeset. I could do this just like I modify the app.config file--in the AfterGet target override. It would be so simple and elegant--just one simple task to add to what I already had!

Well, for some reason I couldn't get Xml.ModifyFile to work on the csproj file, so I just switched over to File.Regex.

I also did find I had to make a minor tweak of the Regex that extracted the version. My Regex only worked if the Major, Minor, and build numbers were single digits. I just had to add a "+" after each bracket ("]") for it to work.

Finally, I had modified the Copy tasks that deployed the files to their deployment site so that only the necessary files would be copied, instead of every file (which originally included all DLLs and EXEs--I only needed the .DEPLOY and related files).

The template for this Team Build project can be found at http://docs.google.com/View?id=ddzhk5g7_2dgt2sxdj

I've uploaded the C# source code for the custom RegexCapture task to http://docs.google.com/View?id=ddzhk5g7_3gqtnz9fs

Friday, June 26, 2009

Ico to PNG conversion

http://www.converticon.com for converting icon files to png.

Originally had http://www.icopng.com/index.php, but that seems to be offline, now.

Image editor to extend MS Paint (transparency & animation)

Generally, for my purposes the image editor in MS Paint or Visual Studio is adequate, but both these fail to provide transparency for GIFs or PNG files. http://www.online-image-editor.com/index.cfm is a great way to add this functionality. Looks like it'll also do GIF animations.

TabControl and the Appearance property

How insane. I had set the Appearance property of the Tabcontrol to "Buttons" and thought, "That looks cool!" I also set the Alignment to "Bottom". Something different and pretty slick-looking.

But then I spent the next few hours trying to figure out why my custom control that I dropped on one of these tab pages wouldn't render. I kept thinking there was something wrong with the custom control. Finally, after a Google, http://msdn.microsoft.com/en-us/library/system.windows.forms.tabcontrol.appearance(VS.71).aspx explained it all.

Selecting "Buttons" or "FlatButtons" for the TabControl's Appearance property requires that the Alignment be "Top". Of course, the notation just says that the alignment must be "Top" or the "Buttons" won't display correctly. It doesn't say anything about any other controls, which is what I experienced.

Alignment to "Top" if altering the Appearance on the TabControl to Buttons or FlatButtons.

Wednesday, June 24, 2009

Finding a control within a control

I have a case where I need to get a particular control out of a container of controls. I don't know the name of the control, although I am prefixing it with a specific pattern, because I'm adding the control dynamically, and there could be any number of these controls.

I found Control.ControlCollection.Find, but the Help for it (http://msdn.microsoft.com/en-us/library/system.windows.forms.control.controlcollection.find.aspx) did not give any examples using a wildcard matching. Yes, I could iterate through all controls, but that seems inefficient. Yes, I could use LINQ, but this project has the limitation of being required to be .NET 2.0, not .NET 3.5.

So, time for the sandbox. Simple test: I create a form, with a groupbox (groupBox1) and put three controls in it. One Button control named "button1" and two CheckBox controls named "checkBox1" and "checkBox2". Then ran this code:

Control

[] ctl = groupBox1.Controls.Find("checkBox*", true

);

Control[] ctl1 = groupBox1.Controls.Find("checkBox1", true

);

Control[] ctl2 = groupBox1.Controls.Find("checkBox2", true);

ctl had no elements, while ctl1 and ctl2 both had one element (due to the exact match). I also tried changing "checkBox*" to just "checkBox" with the same result.

Well, this was rather annoying. After all, what's the point of returning an array, if the best you're going to do is return one item?

Then, on an whim I tried the following code:

this

.checkBox1.Name = "test"

;

this.checkBox2.Name = "test"

;

Control[] ctl = groupBox1.Controls.Find("test", false);

And good news! I got two items back!

So the trick in my case was when I go to generate my dynamic control, I always set the "Name" property the same. Then Controls.Find will find all matches, since you cannot wildcard it.


ClickOnce and Team Systems Summary

Had a new, simple ClickOnce App to add to Team Systems, so I'd thought it a good time to summarize what I've learned from previous posts.

1. Log into the build machine as the tfsbuild user.
2. Open the solution from source control in Visual Studio and publish. This sets up the ClickOnce publish key. This step can be circumvented with the use of MAGE or MAGEUI, but who wants to go through all that when this works.
3. Copy a Team Build project from a previous ClickOnce build project (or use some kind of template, or something).
- Set up your landing page
- If the ClickOnce project references a separate project within the solution, be sure to define both the SolutionToBuild and SolutionToPublish tags. (Otherwise, just the SolutionToPublish is needed).
- Define the Application Title, Support URL, Company, ClickOnceURL, and any other application and deployment-specific properties.
- Modify App.config settings in the AfterGet target override. Also, obtain Version definition in the AfterGet.
- In the AfterDropBuild target override, copy all files from the drop folder to the deployment folder(s).

This pretty much summarizes what works for me.

I promised to go through each tag to explain everything. I did stumple onto http://msdn.microsoft.com/en-us/magazine/dd458792.aspx today, which appears to be a terrific tutorial. I wish I had found it a few weeks (or even months) ago. Because of how good this link is, I'm going to supply only the Cliff Notes version.

In detail, the meaning of each tag in a Team Systems Build project:

<Project
Defines the project.
Attributes:
DefaultTargets - Ignored by Team Systems 2008. This is only used by MSBuild if run directly. Team Systems Build always uses only EndToEndIteration. This attribute defines which target MSBuild should use as its main entry point.

<Import
Specifies include projects files, which are used to define extension tasks.
Attributes:
Project - Defines the project file to import.

<RunCodeAnalysis
Defines whether or not the build will run Code Analysis.

<RunTest
<MetaDataFile
<TestList
Enables/disables the running of tests. Personally, I think this is a great feature to have, but unfortunately, it's often quite difficult to put together adequate tests in a system that integrates with user interaction and separate systems, such as the OpenVMS systems my shop uses.

<Message
Adds a message to the build log
Attributes:
Text - the message.

<ItemGroup

<PropertyGroup

<SolutionToBuild
Defines the solution file that should be compiled.

<SolutionToPublish
Defines the solution file that is to be published.

<ConfigurationToBuild
Defines whether the soution should be built for Debug or Release and for what kind of CPU.

<Target
Overridable point that MSBuild runs.
Attributes:
Name - the name of the target.

<UsingTask
Defines a custom task.
Attributes:
Assembly File - Defines the dll that includes the custom task.
TaskName - Defines the task name, which must be qualified with the full namespace.

<Custom tag
Any custom task defined in a DLL that is specified in a <UsingTask tag. I'll go into detail in a future post as to how to create your own custom tasks.

<Copy
Task to copy file(s)

<CreateProperty
Creates a property

<CreateItem
Creates an ItemList.


http://msdn.microsoft.com/en-us/magazine/dd150090.aspx is a good article on Team Build 2008. If you haven't already figured it out, I'm using Team Build 2008.

This pretty much covers all I care about.

TODO: detail about custom tasks.
TODO: supply nice template Build Projects--One for a ClickOnce application, and one for a web application. I've still got a bit of refining on my current project file in that my task to copy the files from the drop folder to the web cluster for final deployment copies too much and clutters things up (it doesn't really hurt anything, but it's really not right). So I've got to take a little to configure an "Exclude" attribute correctly. Once I've got that done, I should be able to set up a template file.

Russ

Tuesday, June 23, 2009

ClickOnce, Team Systems, and Versioning

You'd think everyone has only one deployment. In my last post, I covered how to set up a Team Build Type for a ClickOnce Application in Team Systems that allows you to modify the app.config file for multiple deployments. However, I had set up the versioning so that Major, minor, and build were stored in the Build Type project file, and the revision came out of the changeset.

Unfortunately, what I discovered was that I'd have to modify the project files for all deployements when the version for Major, minor, or build changed. Since I already had 20 deployments, with the number expected to reach nearly 100, this was unacceptable. I needed a way to store version information in a single file accessible from the build.

The best solution for me is to store it in the AssemblyInfo.cs file of the ClickOnce project. This data is easily modifiable from Visual Studio in the Project's properties, plus ensured the ClickOnce manifest version matched the assembly.

Again Googling, everything was backward. Plenty of links on how to set up a dynamic AssemblyInfo.cs file, or to store version data in a modifyable file as part of the project--no, no, no! This is not what I want! When I run 20 builds, I want all 20 to have the same version number, without having to modify 20 files to do so. Generating the AssemblyInfo.cs file was completely backward and would not ensure consistent version numbers. Using a shared version file was the next best thing, but I'd have to remember to modify it manually. I couldn't have the build change it as there would be a different build number for each of the 20 deployments.

Storing it in the AssemblyInfo.cs file was best as I could easily remember to always manually modify it. It would be even better if I could somehow replace the revision with the changeset number (which the project file already gets anyhow), but I can live without it.

--I can't believe how painful it is to find the information you need--

I thought of XmlRead from MSBuild.Community.Tasks (from http://msbuildtasks.tigris.org/), but AssemblyInfo.cs is in C#, not in XML. There was also a RegexMatch task that could be a possibility, but working with Regex is always nasty. Kind of makes me feel like I'm programming in Malbolge whenever I see it.

Okay, a RegEx tester I had came up with this pattern for extracting Major, minor, and build from AssemblyInfo.cs:(\[assembly\: AssemblyVersion\(.[0-9]\.[0-9]\.[0-9])?[0-9]\.[0-9]\.[0-9]

Unfortunately, after much painful work and finally looking at the source code from msbuildtasks.tigris.org, I discovered that the RegexMatch didn't return the capture match from the Regex expression, but the entire line that the Regex expression found a match on. Useless!

So I finally gave in and created my own custom task--which apparently I should have done in the first place and saved myself the headache of trusting others.

I won't go into the code for the custom task--I'll save something like that for another post. But basically it returned all captures found on a string with a Regex expression.

I used the ReadLinesFromFile task from the Microsoft.Sdc.Tasks to import the AssemblyInfo.cs file into an ItemGroup, which I converted to a PropertyGroup with a CreateProperty task, then passed this property to my custom task.

So, I had the following added:

<ReadLinesFromFile File="$(ConfigDir)/Properties/AssemblyInfo.cs">
<
Output TaskParameter="Lines"
ItemName="ItemsFromFile" />
</ReadLinesFromFile>



<!-- custom task below -->
<RegexCapture Input="@(ItemsFromFile)" Expression="(\[assembly\: AssemblyVersion\(.[0-9]\.[0-9]\.[0-9])?[0-9]\.[0-9]\.[0-9]">
<
Output ItemName ="VersionData" TaskParameter="Output" />
</
RegexCapture>

After a painful mess dealing with the confusion difference between "@" and "$", I finally had success http://msdn.microsoft.com/en-us/library/0k6kkbsd.aspx is a good MSBuild reference. http://msdn.microsoft.com/en-us/library/bb546106.aspx shows the special characters reference.

I used CreateProperty to convert the ItemList from the RegexCapture to a property, then referred to this property on the Regex that modified the version on the Landing Page, and on the ApplicationVersion of the MSBuild.

The revision was already in the project, using TfsVersion from MSBuild.Community Tasks from http://msbuildtasks.tigris.org/. I just concatenated this on the version information I grabbed from the AssemblyInfo file, thus ensuring each build would use an incrementing revision.

My next post: put this and my previous post together in one simplifed post, with details of what everything means.


Friday, June 19, 2009

ClickOnce and Team Systems Build

I'm going nuts on this one. I basically just need to build a ClickOnce application, but I need to modify the .config file as I actually need about 100 different deployments of the same application where the only variation is one setting in the .config file. The different deployments are needed for other reasons as well--which I'm not going to go into.

I've got it all working by overriding the AfterCompile target, but I'm not having success with the .config file modification. I can modify it fine, but it occurs at the wrong time or with the wrong file--the compile picks it up and creates the config.deploy without the modification. I found out the hard way that you can't modify a config.deploy file. If you do, you muck up the hash value that is stored in the manifest--the file's hash won't match the manifest's hash, and the application will refuse to install.


I Googled for help, and it seems everyone has advice, but none if it was very useful. Most of it was just restating

I found this one: http://coolthingoftheday.blogspot.com/2006/04/mageui-can-be-your-clickonce-stage-to.html, but it was pretty useless--just told me about Mage, but no info on how to use it. http://blog.gatosoft.com/PermaLink,guid,d0a0dd1e-c9ac-4fa9-a408-615454d49702.aspx provided a great sample, but it was designed to run MSbuild only from the command line, not from Team Systems, and the sample Deploy.proj has absolutely no comments to explain the meaning of various settings. It was useless of it to have a "GetVersion" target in there if I didn't understand why it was there. (It looks like it's getting the version fom one of the assemblies and then modifying the ClickOnce Landing page with it--is this assumption correct? or is there more? or am I just completely lost?)

I had also found http://www.codeproject.com/KB/install/QuickClickOnceArticle.aspx, but this was absolutely useless--I had already figured out all it's info just playing around with Visual Studio. That page is for newbies only.

http://blogs.msdn.com/echarran/archive/2006/08/09/693284.aspx seened to have some good clues which might have proved useful, but part of blog with the code got wacked due to the formatting of the website. Cut-and-paste did grab all of the code, at least. Then I compared it to the gatosoft.com link and saw all it was doing was giving another GenerateDeploymentManifest target override. Well, maybe it's something.

http://blogs.microsoft.co.il/blogs/maordavid/archive/2008/09/12/team-build-and-clickonce.aspx was useless--It pointed to someone else's blog that apparently is no longer on the Internet and showed just overriding the AfterCompile target. Yeah, okay--I alread knew that. Now get off of Google.

I didn't need to deal with the signing--I got that to work by cheating--just build the project on the build server using the tfsbuild user in Visual Studio. Once done one time, it never needs done again, and it's a lot easier than trying to figure out how to do it in Mage, or whatever.

Most of my problem is that I don't yet have a good handle on the meaning of the XML tags that are used in an MSBuild project. I don't want to waste my time in some class, and I can't find a nice website that just displays the cliff notes version. Microsoft's MSDN site goes into way too much detail to make sense of it quickly, and it isn't organized to give you a good summary of how to use it. All I want to know is how to get this up and running ASAP the way I need it to. I don't need to know all the excruciation of how to override labels.

I first started with the default .proj file that Team Systems creates when you create a new build. I tried taking pieces from that sample above, but none if it seems to execute at all. The default .proj file includes an AfterCompile target, which is completely missing from the above sample--so this led me down some wild goose chases trying to figure this out.

I finally got it down to the ClickOnce Landing page being modified and deployed correctly (default.htm), and all the code being correctly deployed--except the .config file still wasn't right. So, off to MSDN to find all the targets I can override--I need to override a target that occurs before the CoreCompile, but after all the sources are retrieved from source control. It also took me awhile to figure out that I needed a <SolutionToPublish> tag under an <ItemGroup> to get MSBuild to publish my solution. <SolutionToBuild> is for building only, which is useful for websites and non-clickonce applications (although I imagine <SolutionToPublish> will probably work for websites--my shop just always copies everything from the drop folder out to the website--well, my shop does it this way thanks to an overpriced consulting firm that claimed to know something and set us up that way--but that's another story).

http://msdn.microsoft.com/en-us/library/aa337604(VS.80).aspx demonstrated that AfterGet occurs exactly where I need it to, so I tried mucking with that.
I love the <Message> tag in these projects. I always add something like "~~" to the text so that I know it's my message and so that I can find it (as opposed to a message from MSBuild). These messages help me to know what's working and what's not.

First I have to deal with figuring out where the app.config file is locally. I'm using Xml.ModifyFile's XPath to modify my setting. This is pretty straight-foward and I might post info about it some other time.

First runthrough using AfterGet is simple: Message saying we're modifying App.config, then Xml.ModifyFile the app.config. Gotta do the source check-in dance and queue a new build, but fortunately the build only takes about 30 seconds. FAIL--app.config file not found. Okay, I had just specified "app.config" with no path--let me try to figure out where the Get drops all the sources--I just need to use $(SolutionRoot) as my base, then add the subfolders down to the project with the app.config. Now, remember to check out the .proj file before saving my changes, save my changes, check it back in, queue the build and wait 30 seconds--FAIL.

This time is some kind of compiler error:

CSC(0,0): error CS0006: Metadata file 'C:\Documents and Settings\tfsbuild\Local Settings\Temp\xxx.dll' could not be found

(xxx is the name of one of the referenced dlls).

Time to Google. Found http://msdn.microsoft.com/en-us/library/a92dycyz(VS.80).aspx. It says that the dll was not found. Oh, cute. This particular dll should have been compiled with this project. Even worse, the particular project that was failing was one I didn't even care if it didn't compile--it was a "sandbox" project--just something I set up to interactively test some of the custom controls. I have several possible solutions: 1. work my tail off trying to figure out how to overcome this goofy error, 2. work my tail off trying to figure out how to suppress the compile of this sandbox project, or 3. delete the project from source control. Being the lazy man I am, I opted for #3. I don't really need it anymore, anyhow. Zap! check-in, queue new build.

FAIL. Same compiler error, but this time with my critical ClickOnce project. The dll is project that is part of my solution, and this ClickOnce application references it--so why am I having a problem? I did check the app.config (based on the BuildLog.txt messages) and it is getting modified. It doesn't help tha this log file is over 1000 lines long.

Options: 1. Figure out why my ClickOnce project is not finding the DLL from another project in the same solution, or 2. Compile this project in Visual Studio and put the DLL on the ClickOnce project and reference it there. #2 is the lazy man's way of doing it, but only if the dll isn't going to change much. Unfortunately, most of the development is going on in this DLL (it's all the custom controls for the UI), so it's worth my effort to at least make a stab at option 1.

The log shows the dll getting built without error. The message "Skipping unpublishable project" is drawing my attention, but I'm moving on. Immediately after I see that it is building my ClickOnce application. The log shows "Project reference" and states the project for the dll. However, I see "Task 'MSBuild' skipped, due to false condition;" referencing the dll project, with a lot of convoluted logic explaining the false condition: @(NonVCProjectReference) !='' (evaluated to 'my dll project file' != '', a false condition) , but also it bypassed because it wasn't building inside VS. Entry is a wild-goose. moving on...I see "Done building target "ResolveProjectReferences" in project" with reference to my ClickOnce project. Promising--according to this the ClickOnce app should compile. Nothing to explain why the dll can't be found. The ugly thing is: it worked before I mucked with the app.config file. But mucking with the app.config file should have nothing to do with not being able to find the dll. Okay, let's get cute--I had removed the <SolutionToBuild> tag. Let's add it back in and see what happens. Good thing no one is paying attention to the Team Systems "Work items' thing--I've probably got hundreds by now.

Adding <SolutionToBuild> worked, and yeah! my config.deploy has the correct setting in it (intentionally different from what is in source control). Finally--for the install test. SUCCESS!!!

Okay to recap:

This is how to create a Build in Team Systems for a ClickOnce application that will modify a setting in the app.config file:

1. Log into the build machine as the tfsbuild user, open Visual Studio and publish the project. This handles the signing of the keys to the project. You only need to do this once for the project.

2. Define both the <SolutionToBuild> and the <SolutionToPublish> tags.

3. Define the "AfterGet" target override. You can use Xml.ModifyFile from Microsoft.Sdc.Tasks.dll, which comes from http://www.codeplex.com/sdctasks

4. Define the "AfterCompile" target override--this is where all the work really occurs. Best example for defining the "MSBuild" tag is http://blogs.microsoft.co.il/blogs/maordavid/archive/2008/09/12/team-build-and-clickonce.aspx, even though I stated it was rather useless. It does over-list parameters to use. I found that most of the values under the "Properties" attribute to be unnecessary, but at least it lists them, which could prove useful. I've included my working Team systems build project below, although I've obfusicated it. I left most of my variables in there to save myself work, though. Also, Note that I use the "Version" target override to adjust the ClickOnce version automatically, based on the change set. The "GetVersion" target is left in there from stuff I copied, and I don't really know if it does anything.


Finally, note that I don't use MAGE or MAGEUI!!

Sample TFSBuild.Proj file:


<?xml version="1.0" encoding="utf-8"?>
<!-- DO NOT EDIT the project element - the ToolsVersion specified here does not prevent the solutions
and projects in the SolutionToBuild item group from targeting other versions of the .NET framework.
-->
<Project DefaultTargets="Deploy" xmlns="http://schemas.microsoft.com/developer/msbuild/2003" ToolsVersion="3.5">

<!-- Do not edit this -->
<Import Project="$(MSBuildExtensionsPath)\Microsoft\VisualStudio\TeamBuild\Microsoft.TeamFoundation.Build.targets" />
<Import Project="$(MSBuildExtensionsPath)\MSBuildCommunityTasks\MSBuild.Community.Tasks.Targets"/>

<!-- Do i need this?
<Import Project="$(MSBuildExtensionsPath)\Microsoft\VisualStudio\v8.0\TeamBuild\Microsoft.TeamFoundation.Build.targets" />
<Import Project="$(MSBuildExtensionsPath)\MSBuildCommunityTasks\MSBuild.Community.Tasks.Targets"/>
-->

<UsingTask AssemblyFile="Microsoft.Sdc.Tasks.dll" TaskName="Microsoft.Sdc.Tasks.Xml.ModifyFile"/>
<UsingTask AssemblyFile="Microsoft.Sdc.Tasks.dll" TaskName="Microsoft.Sdc.Tasks.File.Replace"/>

<ProjectExtensions>

<!-- Team Foundation Build Version - DO NOT CHANGE -->
<ProjectFileVersion>2</ProjectFileVersion>

<!-- DESCRIPTION
This property is included only for backwards compatibility. The description of a build definition
is now stored in the database. For compatibility with V1 clients, keep this property in sync with
the value in the database.
-->
<Description></Description>

<!-- BUILD MACHINE
This property is included only for backwards compatibility. The default machine used for a build
definition is now stored in the database, as the MachineName property of the definition's
DefaultBuildAgent. For compatibility with V1 clients, keep this property in sync with the value
in the database.
-->
<BuildMachine>UNKNOWN</BuildMachine>

</ProjectExtensions>

<PropertyGroup>

<!-- TEAM PROJECT
This property is included only for backwards compatibility. The team project for a build
definition is now stored in the database. For compatibility with V1 clients, keep this property in
sync with the value in the database.
-->
<TeamProject>put your Team Systems Project name here</TeamProject>

<!-- BUILD DIRECTORY
This property is included only for backwards compatibility. The build directory used for a build
definition is now stored in the database, as the BuildDirectory property of the definition's
DefaultBuildAgent. For compatibility with V1 clients, keep this property in sync with the value
in the database.
-->
<BuildDirectoryPath>UNKNOWN</BuildDirectoryPath>

<!-- DROP LOCATION
This property is included only for backwards compatibility. The drop location used for a build
definition is now stored in the database. For compatibility with V1 clients, keep this property
in sync with the value in the database.
-->
<DropLocation>\\UNKNOWN\drops</DropLocation>

<!-- TESTING
Set this flag to enable/disable running tests as a post-compilation build step.
-->
<RunTest>false</RunTest>

<!-- CODE ANALYSIS
Set this property to enable/disable running code analysis. Valid values for this property are
Default, Always and Never.

Default - Perform code analysis as per the individual project settings
Always - Always perform code analysis irrespective of project settings
Never - Never perform code analysis irrespective of project settings
-->
<RunCodeAnalysis>Default</RunCodeAnalysis>

<!-- Additional Properties -->

<!-- WorkItemType
The type of the work item created on a build failure.
-->
<WorkItemType>Bug</WorkItemType>

<!-- WorkItemFieldValues
Fields and values of the work item created on a build failure.

Note: Use reference names for fields if you want the build to be resistant to field name
changes. Reference names are language independent while friendly names are changed depending
on the installed language. For example, "System.Reason" is the reference name for the "Reason"
field.
-->
<WorkItemFieldValues>System.Reason=Build Failure;System.Description=Start the build using Team Build</WorkItemFieldValues>

<!-- WorkItemTitle
Title of the work item created on build failure.
-->
<WorkItemTitle>Build failure in build:</WorkItemTitle>

<!-- DescriptionText
History comment of the work item created on a build failure.
-->
<DescriptionText>This work item was created by Team Build on a build failure.</DescriptionText>

<!-- BuildLogText
Additional comment text for the work item created on a build failure.
-->
<BuildlogText>The build log file is at:</BuildlogText>

<!-- ErrorWarningLogText
Additional comment text for the work item created on a build failure.
This text will only be added if there were errors or warnings.
-->
<ErrorWarningLogText>The errors/warnings log file is at:</ErrorWarningLogText>

<!-- UpdateAssociatedWorkItems
Set this flag to enable/disable updating associated workitems on a successful build.
-->
<UpdateAssociatedWorkItems>true</UpdateAssociatedWorkItems>

<!-- AdditionalVCOverrides
Additional text for the VCOverrides file generated for VC++ projects.
-->
<AdditionalVCOverrides></AdditionalVCOverrides>

<!-- CustomPropertiesForClean
Custom properties to pass to the MSBuild task while calling the "Clean" target for all solutions.
The format should be: PropertyName1=value1;PropertyName2=value2;...
-->
<CustomPropertiesForClean></CustomPropertiesForClean>

<!-- CustomPropertiesForBuild
Custom properties to pass to the MSBuild task while calling the default targets for all solutions.
The format should be: Property1=value1;Property2=value2;... To pass custom properties to
individual solutions, use the Properties metadata item of the SolutionToBuild ItemGroup.
-->
<CustomPropertiesForBuild></CustomPropertiesForBuild>

</PropertyGroup>

<ItemGroup>
<!-- SOLUTIONS
The paths of the solutions to build. Paths can be server paths or local paths, but server paths
relative to the location of this file are highly recommended. To add/delete solutions, edit this
ItemGroup. For example, to add a solution MySolution.sln, add the following line:

<SolutionToBuild Include="$(BuildProjectFolderPath)/path/MySolution.sln" />

To change the order in which the solutions are built, modify the order in which the solutions
appear below.

To call a target (or targets) other than the default, add a metadata item named Targets. To pass
custom properties to the solution, add a metadata item named Properties. For example, to call
the targets MyCustomTarget1 and MyCustomTarget2, passing in properties Property1 and Property2,
add the following:

<SolutionToBuild Include="$(BuildProjectFolderPath)/path/MySolution.sln">
<Targets>MyCustomTarget1;MyCustomTarget2</Targets>
<Properties>Property1=Value1;Property2=Value2</Properties>
</SolutionToBuild>
-->
<SolutionToBuild Include="$(BuildProjectFolderPath)/path/MySolution.sln">
<Targets></Targets>
<Properties></Properties>
</SolutionToBuild>

<SolutionToPublish Include="$(SolutionToBuild)" />
</ItemGroup>

<ItemGroup>
<!-- CONFIGURATIONS
The list of configurations to build. To add/delete configurations, edit this value. For example,
to add a new configuration, add the following lines:

<ConfigurationToBuild Include="Debugx86">
<FlavorToBuild>Debug</FlavorToBuild>
<PlatformToBuild>x86</PlatformToBuild>
</ConfigurationToBuild>

The Include attribute value should be unique for each ConfigurationToBuild node.
-->
<ConfigurationToBuild Include="ReleaseAny CPU">
<FlavorToBuild>Release</FlavorToBuild>
<PlatformToBuild>Any CPU</PlatformToBuild>
</ConfigurationToBuild>

</ItemGroup>

<ItemGroup>
<!-- TEST ARGUMENTS
If the RunTest property is set to true then the following test arguments will be used to run
tests. Tests can be run by specifying one or more test lists and/or one or more test containers.

To run tests using test lists, add MetaDataFile items and associated TestLists here. Paths can
be server paths or local paths, but server paths relative to the location of this file are highly
recommended:

<MetaDataFile Include="$(BuildProjectFolderPath)/HelloWorld/HelloWorld.vsmdi">
<TestList>BVT1;BVT2</TestList>
</MetaDataFile>

To run tests using test containers, add TestContainer items here:

<TestContainer Include="$(OutDir)\HelloWorldTests.dll" />
<TestContainer Include="$(SolutionRoot)\TestProject\WebTest1.webtest" />
<TestContainer Include="$(SolutionRoot)\TestProject\LoadTest1.loadtest" />

Use %2a instead of * and %3f instead of ? to prevent expansion before test assemblies are built
-->

</ItemGroup>
<PropertyGroup>
<Major>1</Major>
<Minor>1</Minor>
<Build>5</Build>
</PropertyGroup>
<PropertyGroup>
<!-- TEST ARGUMENTS
If the RunTest property is set to true, then particular tests within a
metadata file or test container may be specified here. This is
equivalent to the /test switch on mstest.exe.

<TestNames>BVT;HighPriority</TestNames>
-->

<Environment>-DEV</Environment>
<AppDescription>Your application description</AppDescription>
<SourceDir>E:\SystemVMS\DevBuildDrop\Release</SourceDir>
<PublishDir>$(SourceDir)\Publish</PublishDir>
<SupportUrl>http://whatever.com</SupportUrl>

<!-- signing certificate below -->
<SigningCert>ClickOnce_TemporaryKey.pfx</SigningCert>
<Company>My Company, Inc.</Company>

<SettingsFile>E:\SystemVMS\DevBuildDrop\settings.xml</SettingsFile>
<SolutionName>MySolution</SolutionName>

<AppConfigFile>app.config</AppConfigFile>

<CustomSetting>My setting</CustomSetting>




</PropertyGroup>

<PropertyGroup>

<!--<ConfigDir>$(SolutionRoot)\..\Binaries\Release\</ConfigDir>-->
<ConfigDir>$(SolutionRoot)\$(SolutionName)$(Environment)\$(SolutionName)\</ConfigDir>
<LocalClickOnceVirtualRootDir>ClickOncePublishing</LocalClickOnceVirtualRootDir>
<RemoteClickOnceVirtualRootDir>\\whatever\e$\Services\$(SolutionName)2\</RemoteClickOnceVirtualRootDir>
<ClickOnceUrl>http://whatever.com/$(SolutionName)/default.htm</ClickOnceUrl>
<ClickOnceApplicationUrl>$(ClickOnceUrl)$(SolutionName).application</ClickOnceApplicationUrl>

<ImageDirectory>$(SolutionRoot)\$(SolutionName)$(Environment)\Referenced Binaries</ImageDirectory>

<ClickOnceAppTitle>My ClickOnce Application</ClickOnceAppTitle>

<CurrentProdBuildFile>E:\SystemVMS\DevBuildDrop\settings.xml</CurrentProdBuildFile>

</PropertyGroup>
<ItemGroup>
<!-- ADDITIONAL REFERENCE PATH
The list of additional reference paths to use while resolving references. For example:

<AdditionalReferencePath Include="C:\MyFolder\" />
<AdditionalReferencePath Include="C:\MyFolder2\" />
-->
</ItemGroup>
<ItemGroup>
<ClickOnceInstallationFiles Include="$(SolutionName).application"/>
<ClickOnceInstallationFiles Include="$(SolutionName).exe.manifest"/>
<ClickOnceInstallationFiles Include="setup.exe"/>
<ClickOnceInstallationFiles Include="default.htm"/>
<ClickOnceLandingPage Include="$(SolutionRoot)\$(SolutionName)$(Environment)\Referenced Binaries\ClickOnceLandingPage.htm"/>

</ItemGroup>

<Target Name="Version">
<TfsVersion LocalPath="$(SolutionRoot)">
<Output TaskParameter="Changeset" PropertyName="Revision"/>
</TfsVersion>

</Target>

<Target Name="GetVersion">
<Message Text=" ~~~ Getting version info..."/>
<GetAssemblyIdentity AssemblyFiles="@(GetVersionAssembly)">
<Output TaskParameter="Assemblies"
ItemName="GetVersionAssemblyInfo"/>
</GetAssemblyIdentity>
</Target>


<Target Name="AfterGet">
<Message Text=" ~~~ Doing After Get"/>

<!-- Edit Config File here.-->
<Message Text=" ~~~ Modifying Config file for MyUserSetting: AppConfigFile=$(ConfigDir)$(AppConfigFile)"/>
<ModifyFile
Path="$(ConfigDir)$(AppConfigFile)"
XPath="/configuration/userSettings/$(SolutionName).Properties.Settings/setting[@name='MyUserSetting']/value"
NewValue="$(CustomSetting)"
Force="true" />


</Target>
<Target Name="AfterCompile" DependsOnTargets="Version">
<Message Text=" ~~~ BuildProjectFolderPath=$(BuildProjectFolderPath)"/>
<Message Text=" ~~~ SolutionRoot=$(SolutionRoot)"/>



<Message Text=" ~~~ copying @(ClickOnceLandingPage) to $(RemoteClickOnceVirtualRootDir)\default.htm"/>
<Copy SourceFiles="@(ClickOnceLandingPage)"
DestinationFiles="$(RemoteClickOnceVirtualRootDir)\default.htm"/>
<Message Text=" ~~~ Modifying landing page for app name, title, and version"/>
<!-- TODO: change to use File.RegEx -->
<ModifyFile
Path="$(RemoteClickOnceVirtualRootDir)\default.htm"
RegularExpression="#APPLICATION_NAME#"
NewValue="$(SolutionName)"
Force="true"/>
<ModifyFile
Path="$(RemoteClickOnceVirtualRootDir)\default.htm"
RegularExpression="#TITLE#"
NewValue="$(ClickOnceAppTitle)"
Force="true"/>
<ModifyFile
Path="$(RemoteClickOnceVirtualRootDir)\default.htm"
RegularExpression="#VERSION#"
NewValue="$(Major).$(Minor).$(Build).$(Revision)"
Force="true"/>
<Message Text=" ~~~ Building"/>
<MSBuild
Projects="$(SolutionRoot)/$(SolutionName)$(Environment)/$(SolutionName)/$(SolutionName).csproj"
Properties="Configuration=%(ConfigurationToBuild.FlavorToBuild);PublishDir=$(RemoteClickOnceVirtualRootDir);OutDir=$(RemoteClickOnceVirtualRootDir);ApplicationVersion=$(Major).$(Minor).$(Build).$(Revision);"
Targets="Publish" />


</Target>

</Project>