Thursday, December 9, 2010

File search: something I discovered in VS2010

Anyone who uses Visual Studio should be familiar with the Find in Files functionality (shortcut: Ctrl-Shift-F ).



Everyone has used it at some point, usually when you need to search all the files in your solution for a particular string. Those of you more adventurous may even have changed the file filter at the bottom, or searched using a regex. Pat yourself on the back, you are awesome.

Today i found a feature of it that has been there for a few years but i had never seen it before. This feature kicks ass. I used it to search specific folders on the file system that were outside of the solution. How many times have you thought to yourself, "hmmm.... where was that bit of code in that other project that did that certain thing...?", so you break out Windows File Explorer to search your base projects folder, only to be returned a bunch of crap? Well, you don't even have to leave your IDE to do it. Start up Find in Files, and on the Look in combo (that is usually set to Entire Solution or Current Document), click the ellipsis to the right of it:



and you will be presented with a dialog you never knew existed.



Just navigate your file system using the Available folders combo and its up-a-level button, and select whichever folders or drives you want to search from the listbox. Hit OK then Find All and Visual Studio will start searching for you.



Super user tips:
  • Don't forget that you can still use the file filter box, if searching large folder structures you may want to limit what files you are checking.
  • If you want to avoid that browse dialog, you can just enter a semi-colon delimited set of paths straight into the Look in combo
  • You can save a set of folders/paths for later use. In the browse dialog, select your paths, then enter a name for them in the Folder set combo and click Apply. You can then reselect that entry in later searches.

Friday, February 26, 2010

Streamlining property notifications in MVVM

Anyone doing Silverlight or WPF will know that the MVVM pattern is all the rage at the moment. So lots of people out there are now writing ViewModels to bind to their Views, which means they will be writing this sort of thing:
public string MyProperty
{
get { return _myProperty; }
set
{
bool changed = value != _myProperty;
if (changed)
{
_myProperty = value;
OnPropertyChanged("MyProperty");
}
}
}


When you have a ViewModel that has anything more than just a few properties, you end up having a LOT of code that while not an exact duplicate, it is doing the exact same thing to different arguments, which may or may not be of the same type. Then you multiply this problem by the number of Views you have, and the process of writing a ViewModel becomes very tedious indeed.

When you have the same code duplicated, you look to see if you can refactor it into a function, and the lines of duplicated code become a call to that new function. And when you are doing the exact same thing to different types, you look to see if you can employ generics.

To solve this issue i came up with the following:
protected void SetProperty(ref T newValue, ref T currentValue, bool notify, string propertyName, params string[] additionalProperties)
{
bool changed = notify && ((newValue != null && !newValue.Equals(currentValue)) || (newValue == null && currentValue != null));
currentValue = newValue;
if (changed)
{
OnPropertyChanged(propertyName);
if (additionalProperties != null)
foreach (string additionalProperty in additionalProperties)
OnPropertyChanged(additionalProperty);
}
}


It's not rocket science, and others have probably come up with the same kind of function, but i love it because it now saves me so much time and eliminates so much repetition. I include this function in to a base class that all my ViewModels inherit from, here it is with a sample on how to use it:
public class ViewModelBase : INotifyPropertyChanged
{

protected void SetProperty(ref T newValue, ref T currentValue, bool notify, string propertyName, params string[] additionalProperties)
{
bool changed = notify && ((newValue != null && !newValue.Equals(currentValue)) || (newValue == null && currentValue != null));
currentValue = newValue;
if (changed)
{
OnPropertyChanged(propertyName);

if (additionalProperties != null)
foreach (string additionalProperty in additionalProperties)
OnPropertyChanged(additionalProperty);
}
}

protected virtual void OnPropertyChanged(string propertyName)
{
if (this.PropertyChanged != null)
this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}

public event PropertyChangedEventHandler PropertyChanged;
}

public class MyRealViewModel : ViewModelBase
{
public int NumberOfItems
{
get { return _numItems; }
set { SetProperty(ref value, ref _numItems, true, "NumberOfItems"); }
}

public bool SomeKindOfFlag
{
get { return _flag; }
set { SetProperty(ref value, ref _flag, false, ""); }
}

public LightSabre WeaponOfChoice
{
get { return _weapon; }
set { SetProperty(ref value, ref _weapon, true, "WeaponOfChoice", "SomeKindOfFlag", "NumberOfItems"); }
}

private bool _flag;
private int _numItems;
private LightSabre _weapon;
}

public class LightSabre
{
public string LightSabreName { get; set; }

public override bool Equals(object obj)
{
if (obj != null && obj as LightSabre != null)
return ((LightSabre)obj).LightSabreName == this.LightSabreName;

return false;
}
}

Monday, January 18, 2010

Wow, so many posts in so few days.... someone should give me a medal.

After a plague of errors when trying to deploy some IIS7 hosted WCF services to a machine, and then using VS2008 from my local machine to create a ServiceReference, i thought i should list some of the issues and what fixed them. Any issues worth mentioning will get their own blog posts, so they will be short and sharp.

The document at the url http://192.168.0.999/ProjectServices/WebServices/MyWCFService.svc was not recognized as a known document type.
The error message from each known type may help you fix the problem:
- Report from 'DISCO Document' is 'There was an error downloading 'http://server2008.workflow.local/ProjectServices/WebServices/MyWCFService.svc?disco'.'.
- The request failed with HTTP status 502: Proxy Error ( Host was not found ).
- Report from 'WSDL Document' is 'The document format is not recognized (the content type is 'text/html; charset=UTF-8').'.
- Report from 'http://192.168.0.999/ProjectServices/WebServices/MyWCFService.svc' is 'The document format is not recognized (the content type is 'text/html; charset=UTF-8').'.
- Report from 'XML Schema' is 'The document format is not recognized (the content type is 'text/html; charset=UTF-8').'.
Metadata contains a reference that cannot be resolved: 'http://192.168.0.999/ProjectServices/WebServices/MyWCFService.svc'.
Content Type application/soap+xml; charset=utf-8 was not supported by service http://192.168.0.999/ProjectServices/WebServices/MyWCFService.svc. The client and service bindings may be mismatched.
The remote server returned an error: (415) Cannot process the message because the content type 'application/soap+xml; charset=utf-8' was not the expected type 'text/xml; charset=utf-8'..
If the service is defined in the current solution, try building the solution and adding the service reference again.


It turns out i was missing a mex entry from my service endpoint addresses in my web.config. As soon as i added it:

<services>
  <service behaviorConfiguration="BaseServiceBehavior" name="MyProject.Web.Services.MyWCFService">
    <endpoint binding="basicHttpBinding" bindingConfiguration="MyWCFService" contract="MyProject.Web.Services.IMyWCFService" />
    <endpoint address="mex" binding="mexHttpBinding" contract="IMetadataExchange" />
  </service>
</services>


the Add Service Reference started working. W00t.

*note for those who spotted the invalid url: as usual, identities have been changed to protect the innocent.

Tuesday, January 12, 2010

Better file searching in Windows 7

Wow, i just found another cool feature in Windows 7 - a nice way to do file searching.

I had tried to run a SourceSafe history report to find what files i had changed on a specific project, and of course SourceSafe was being its usual wanky horrendous self and the report was producing nothing. So i move to plan B - use a file search on my local hard drive, and order by date modified. Then i noticed that Windows 7 now allows you to search by a date range:





The syntax is

datemodified:<start date> .. <end date>

you can type this directly in to the search box, the date formats are in the UI locale you are running as. You can also use your mousey mouse to select the date range from the dropdown date selector, but personally i find it faster and more accurate to just type it in.

Quick example time: what did i add to my Zune playlist in the last few months of last year? (did you think i was gonna show a pic of all the code i wrote - hell no!!)


Monday, January 11, 2010

Dynamic ControlTemplate in Silverlight

I have a Silverlight 3 application that uses a grid from DevExpress. I needed to extend the standard AgDataGridColumn so that i could show images, those images being representations of an enumeration.

To achieve this is simple enough - just override the ControlTemplate that is assigned to the DisplayTemplate property of the column. Doing this declaratively (in XAML) is pretty damn simple. But i wasn't doing it that way - as i am using a factory pattern for creating the columns from some agnostic descriptors, i needed to locate, load and assign the ControlTemplate programmatically.

The extended grid column sits in a second assembly. As i had created several templated controls in this controls assembly, the project templates had already created a ResourceDictionary called generic.xaml in the Themes folder, so i intended to use this file to define the ControlTemplate and then just load it in code, using a line of code similar to


ControlTemplate ct = Application.Current.Resources["MyImageColumnTemplate"] as ControlTemplate;

Unfortunately, this didn't work, Application.Current.Resources had no idea about my ControlTemplate. Then i realised that because it was defined in a ResourceDictionary, i needed to load that ResourceDictionary and merge it with the resources associated with the current application. To do that is pretty simple:

ResourceDictionary dict = new ResourceDictionary();
dict.Source = new Uri("path to resource dictionary", UriKind.Absolute);

Application.Current.Resources.MergedDictionaries.Add(dict);


But of course nothing is that simple, right? I found half a dozen blog or forum postings that indicated all i would have to use for my URI was this:

Uri uri = new Uri("pack://application:,,,/MySilverlightControls;component/themes/generic.xaml", UriKind.Absolute);

The scheme is correct, the assembly name is correct, the path is correct, what could go wrong? Well, first i received an error about there being no port number specified:


UriFormatException was unhandled by user code

Invalid URI: A port was expected because of there is a colon (':') present but the port could not be parsed.

A little bit more googlebinging showed me that i probably needed to register the pack scheme – i thought it would already be registered (especially as the Loaded event for several Silverlight components had been fired) but it wasn't. OK, another half step forward:

if (!UriParser.IsKnownScheme("pack"))

UriParser.Register(newGenericUriParser(GenericUriParserOptions.GenericAuthority), "pack", -1);


Once i dropped that bit of code in an appropriate place, that error went away. But then i started to be plagued with vague COM errors depending on how i tried to declare the URIs.


dict.Source = new Uri("/themes/generic.xaml", UriKind.Relative);


dict.Source = new Uri("./../../themes/generic.xaml", UriKind.Relative);


dict.Source = new Uri("../../themes/generic.xaml", UriKind.Relative);

all produced the error


Error HRESULT E_FAIL has been returned from a call to a COM component.


at MS.Internal.XcpImports.CheckHResult(UInt32 hr)


at MS.Internal.XcpImports.SetValue(INativeCoreTypeWrapper obj, DependencyProperty property, String s)


at etc, etc...



dict.Source = new Uri("pack://application:,,,/themes/generic.xaml", UriKind.Absolute);


dict.Source = new Uri("pack://application:,,,/MySilverlightControls;component/themes/generic.xaml", UriKind.Absolute);

both produced the error


Exception from HRESULT: 0x80072EE5

which effectively means "Invalid URI".

So i did what any engineer or scientist should do – remove every variable from the equation until you are left with just the single problematic item, and then try and isolate the problem. I created a new ResourceDictionary
(called Dictionary1.xaml) under the Themes folder, put the MyImageColumnTemplate XAML in there, and went back through my seven different ways of specifying the URI for the ResourceDictionary. Suddenly i had success! This ended up being the code that worked:

XAML:


<ControlTemplate x:Name="MyImageColumnTemplate" >

<Grid MaxHeight="20" MaxWidth="20">

<Grid.Resources>

<localGrid:EnumColumnImageConverter x:Key="ImageContentConverter"/>

</Grid.Resources>

<Image Source="{Binding EditValue, Converter={StaticResource ImageContentConverter}}" />

</Grid>

</ControlTemplate>


C#:

Uri uri = new Uri("/MySilverlightControls;component/themes/Dictionary1.xaml", UriKind.Relative);

ResourceDictionary dict = new ResourceDictionary();

dict.Source = uri;

Application.Current.Resources.MergedDictionaries.Add(dict);

ControlTemplate ct = (ControlTemplate)Application.Current.Resources["MyImageColumnTemplate"];

this.DisplayTemplate = ct;

That URI also worked when i pointed it at generic.xaml. All the other attempted URIs look correct, but none of them would work. Now i had a custom image column that used my custom ControlTemplate, which in turn used my Converter to translate an enumeration into the appropriate image (that image was also extracted from a resource file.... but i had no issue with that!).


Wow, this post was a bit long but i tried my best to keep it simple, and i hope it helps someone out there.