Create shortcut to SL5 OOB App »
Hi,
I’m loving the power of Silverlight 5 Out of Browser (OOB) applications, now with improved file access and pInvoke. Thinking about deploying an OOB app to my users, I was wondering if I could create an OOB app that would launch itself when the OS starts.
Looking at how a user invokes the “Install SL OOB” operation by using the built in Silverlight Right Click menu, or a UI that the developer can create which calls the Application.Install() method, they are given 2 options: Desktop and/or Programs. Both of these essentially create a shortcut that launches the Silverlight OOB application using something like this:
"C:\Program Files (x86)\Microsoft Silverlight\sllauncher.exe" 2237722325.localhost
So I assumed I should be able to call the System.IO.File.Copy operation in my elevated trust SL5 OOB application and copy the link from the desktop or programs to the startup folder, but I was wrong. The System.IO.File.Copy operation throws a security exception if I try to write to the Startup folder?! I thought it was suppose to be able to do anything, but apparently NOT!.
pInvoke to the rescue!
Remembering that I could use Win32 APIs with the new pInvoke capabilities in SL5 elevated trust, I quickly replace the File.Copy with CopyFile from kernel32.dll and got it to work.
Enjoy the code.
JW.
Add this declaration to your class:
[DllImport("kernel32.dll", CharSet = CharSet.Unicode, CallingConvention = CallingConvention.StdCall, SetLastError = true)] [return: MarshalAs(UnmanagedType.Bool)] static extern bool CopyFile( [MarshalAs(UnmanagedType.LPWStr)]string lpExistingFileName, [MarshalAs(UnmanagedType.LPWStr)]string lpNewFileName, [MarshalAs(UnmanagedType.Bool)]bool bFailIfExists);
Add this function:
private void CreateShortcutInStartUP()
{
try
{
Assembly app = Assembly.GetExecutingAssembly();
//string version = app.FullName.Split(',')[1];
//string fullVersion = version.Split('=')[1];
string ApplicationName = string.Format("{0} Application", app.FullName.Split(',')[0]);
if (ApplicationName != "")
{
string linkName = string.Format("{0}.lnk", ApplicationName);
string DesktopShortcutPath = Environment.GetFolderPath(Environment.SpecialFolder.Desktop);
DesktopShortcutPath = Path.Combine(DesktopShortcutPath, linkName);
string StartUpPath = Environment.GetFolderPath(Environment.SpecialFolder.Programs);
StartUpPath = Path.Combine(StartUpPath, "Startup");
//MessageBox.Show(string.Format("StartUpPath = {0}",StartUpPath));
//MessageBox.Show(string.Format("DesktopShortcutPath = {0}", DesktopShortcutPath));
if (!System.IO.Directory.Exists(StartUpPath))
{
MessageBox.Show(string.Format("Cannot find: {0}", StartUpPath));
return;
}
if (!System.IO.File.Exists(DesktopShortcutPath))
{
MessageBox.Show(string.Format("Cannot find: {0}", DesktopShortcutPath));
return;
}
StartUpPath = Path.Combine(StartUpPath, linkName);
//System.IO.File.Copy(ShortcutPath, newShortcutPath, true);
if (CopyFile(DesktopShortcutPath, StartUpPath, false))
{
MessageBox.Show(string.Format("Copied to: {0}", StartUpPath));
}
else
{
MessageBox.Show("Failed to create Startup Shortcut.");
}
}
}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
}
}
Add call it from your startup code.
Silverlight 5 pInvoke USB/Removable Drive detector »
Hi,
A friend of mine asked me if an elevated trust Silverlight 5 application could monitor windows events, specifically when a USB drive is inserted or removed. I naturally said YES, knowing that pInvoke is supported, but decided to take a few moments to write some code.
After searching the web I found a great sample (written by Alexandra Rusina)
here: http://blogs.msdn.com/b/silverlight_sdk/archive/2011/09/27/pinvoke-in-silverlight5-and-net-framework.aspx
I proceeded to create simple class RemovableDriveMonitor which monitors windows messages by creating an actual window class (WNDCLASS) and hooking into its WndProc message stream as Alexandra demonstrated and added a RemovableDriveStatusChanged event to notify the host app of any changes to USB drives.
RemovableDriveMonitor is initialized by the host application which also subscribes to the RemovableDriveStatusChanged event. This even is fired with a different message for “USB Inserted” or “USB Removed”.
Enjoy,
JW.
How to use?
Add a class called RemovableDriveMonitor like so:
using System; using System.Diagnostics; using System.Runtime.InteropServices; namespace SL5Oob { public class RemovableDriveMonitor { //Delegate public delegate void RemovableDriveMonitorHandler(object sender, RemovableDriveDetectionArgs e); //Event public event RemovableDriveMonitorHandler RemovableDriveStatusChanged; private void OnRemovableDriveStatusChanged(string msg) { if (null != RemovableDriveStatusChanged) { RemovableDriveStatusChanged(this, new RemovableDriveDetectionArgs(msg)); } } // Importing a set of necessary native methods from Win32 API. [DllImport("User32", EntryPoint = "CreateWindowEx", CharSet = CharSet.Auto, SetLastError = true)] static extern IntPtr CreateWindowEx(int dwExStyle, string lpszClassName, string lpszWindowName, int style, int x, int y, int width, int height, IntPtr hWndParent, IntPtr hMenu, IntPtr hInst, [MarshalAs(UnmanagedType.AsAny)] object pvParam); [DllImport("user32.dll")] static extern IntPtr DefWindowProc(IntPtr hWnd, int uMsg, IntPtr wParam, IntPtr lParam); [DllImport("user32", CharSet = CharSet.Auto, SetLastError = true)] public static extern short RegisterClass(WNDCLASS wc); // Marshaling the Window structure. [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)] public class WNDCLASS { public int style; public WndProc lpfnWndProc; public int cbClsExtra; public int cbWndExtra; public IntPtr hInstance; public IntPtr hIcon; public IntPtr hCursor; public IntPtr hbrBackground; public string lpszMenuName; public string lpszClassName; } //system detects USB insertion/removal const int WM_DEVICECHANGE = 0x0219; // system detects a new device const int DBT_DEVICEARRIVAL = 0x8000; // device removed const int DBT_DEVICEREMOVECOMPLETE = 0x8004; // Callbacks must have AllowReversePInvokeCalls attribute. [AllowReversePInvokeCalls] private IntPtr Callback( IntPtr hWnd, int msg, IntPtr wparam, IntPtr lparam) { if (msg == WM_DEVICECHANGE) { if (wparam.ToInt32() == DBT_DEVICEARRIVAL) { Debug.WriteLine("USB inserted"); //<<<<<<<<<<<<<<<<<<<<<<<<<< OnRemovableDriveStatusChanged("USB Inserted"); } if (wparam.ToInt32() == DBT_DEVICEREMOVECOMPLETE) { Debug.WriteLine("USB removed"); //<<<<<<<<<<<<<<<<<<<<<<<<<< OnRemovableDriveStatusChanged("USB Removed"); } } return DefWindowProc(hWnd, msg, wparam, lparam); } public delegate IntPtr WndProc( IntPtr hWnd, int msg, IntPtr wParam, IntPtr lParam); // Preventing garbage collection of the delegate private static WndProc dontGCthis; /// <summary> /// Default Constructor /// </summary> public RemovableDriveMonitor() { WNDCLASS wc = new WNDCLASS(); // Preventing garbage collection of the delegate dontGCthis = new WndProc(Callback); wc.lpfnWndProc = dontGCthis; // Note that you need to ensure unique names // for each registered class. // For example, if you open the same plugin // in two different tabs of the browser, // you still should not end up with // two registered classes with identical names. wc.lpszClassName = "foobar" + (new Random()).Next(); RegisterClass(wc); IntPtr createResult = CreateWindowEx(0, wc.lpszClassName, "Window title", 0, 100, 100, 500, 500, IntPtr.Zero, IntPtr.Zero, IntPtr.Zero, 0); } } }
This uses a class RemovableDriveDetectionArgs:
public class RemovableDriveDetectionArgs { public string Message { get; set; } public RemovableDriveDetectionArgs(string msg) { this.Message = msg; } }
In your app, add an instance of RemoveDriveMonitor:
/// <summary> /// local member holding an instance of RemoveDriveMonitor in the host app/page/usercontrol /// </summary> private RemovableDriveMonitor usbMonitor = null;
Then make sure you create an instance of RemovableDriveMonitor and subscribe to the RemovableDriveStatusChanged event:
usbMonitor = new RemovableDriveMonitor(); usbMonitor.RemovableDriveStatusChanged += new RemovableDriveMonitor.RemovableDriveMonitorHandle (usbMonitor_RemovableDriveStatusChanged);
Implement an event sink function like so and present the e.Message in your UI:
void usbMonitor_RemovableDriveStatusChanged(object sender, RemovableDriveDetectionArgs e) { usbMonitorStatusWidget.Text = e.Message; }
Silverlight 5 (OOB) Win32 API GetLogicalDriveStrings »
Hi,
Starting to use Silverlight 5 and enjoying P/Invoke!
We needed the ability to list drive letters on the user’s computer.
Using System.Runtime.InteropServices, we imported the GetLogicalDriveStrings method.
Then we created our own GetLogicalDrives() method which extracts the drive letters from the buffer that is returned.
Here’s the code:
using System.Runtime.InteropServices; using System.Windows; using System.Windows.Controls; using System; using System.Collections.Generic; using System.Diagnostics; namespace SL5Oob { public partial class MainPage : UserControl { [DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)] static extern uint GetLogicalDriveStrings(uint nBufferLength, [Out] char[] lpBuffer); public MainPage() { InitializeComponent(); } private void GetDrives_Click(object sender, RoutedEventArgs e) { GetLogicalDrives(); } private static void GetLogicalDrives() { const int size = 512; char[] buffer = new char[size]; uint code = GetLogicalDriveStrings(size, buffer); if (code == 0) { MessageBox.Show("Call failed"); return; } List<string> list = new List<string>(); int start = 0; for (int i = 0; i < code; ++i) { if (buffer[i] == 0) { string s = new string(buffer, start, i - start); list.Add(s); start = i + 1; } } foreach (string s in list) { Debug.WriteLine(s); } } } }
Enjoy,
JW.
Grid with Rounded Corners–How To? »
Hi,
Borders are cool. One of the most useful features is the ability to set the CornerRadius and get a rounded border.
However, if you have other controls inside the Border, the do not always clip correctly to the rounded corners.
I found a nice article titled: Dynamic Clipping of Rounded Corners in Silverlight which shows a neat technique for adjusting the Grid to fit inside the rounded corners of the Border that contains it.
Enjoy.
JW.
IE9 changes for developers »
Hi,
If you build applications for the Web, like we do, then you know that some time should be spent on your web applications whenever a new version of a Web Browser is release.
IE9 (Internet Explorer 9) has been released with many new and improved features (which you can see here: IE 9 Getting Started, What’s new? ).
As developers, we have been keeping track of the changes and found this resource to be a great list for developers: Internet Explorer 9 Compatibility Cookbook.
We’re working on updating our apps now and will post any interesting things we find. Please post your own experience.
The Net Us Up Team.
Change a style in runtime using Silverlight. »
I was trying to complete a simple task today and got stuck.
The reason was that styles in Silverlight become sealed once they are set and trying to change a style in runtime just caused an error.
After doing some other things, it occured to me, that it may be possible to “copy” the style and add what I needed to it.
In my case, I had a custom control that I used for buttons (with some special behaviors) called AppButton, based on the Silverlight HyperlinkButton.
So a few experiments later I came up with this:
//Get the current style from the App.Resources
Style newStyle = new Style(typeof(AppButton));
newStyle.BasedOn = App.Current.Resources[typeof(AppButton)] as Style;
//Add the properties I need to change in the style
newStyle.Setters.Add(new Setter(AppButton.ForegroundProperty, new SolidColorBrush(Colors.Red)));
//Replace the original style with the newStyle
App.Current.Resources.Remove(typeof(AppButton));
App.Current.Resources.Add(typeof(AppButton), newStyle);
I’m not planning to use this alot, but it solved a problem I had, so I thought I should share.
Happy coding,
JW.
How to set Foreground of DataGrid Column with ValueConverter »
If you need to change the Foreground (Color) of a DataGrid Column based on its value you will probably encounter a problem when you try to bind your DataGridTextColumn.Foreground property to a ValueConverter. I’ll show you how we do it and hopefully save you an hour or two.
Tools used?
Download Code: NetUsUp_Silverlight_TipsAndTricks.zip
See How To:
- Create a ValueConverter between DateTime? (Nullable DateTime) and Brush (Foreground property’s type).
- Bind a ValueConverter to a DataGrid Column’s Foreground property.
Creating our ValueConverter:
We create a ValueConverter which return a Black SolidColorBrush unless the DateTime value matches today or earlier, in which case it returns Red.
Note: Since we have no need to convert the value back, we just return the value in the ConvertBack() method.
1: /// <summary> 2: /// Converts between Nullable DateTime and Color Brush 3: /// </summary> 4: public class DateTimeToColorValueConverter: IValueConverter 5: {
6: /// <summary> 7: /// Converts value (in our case DateTime?) into a Brush 8: /// for binding to Foreground of text element. 9: /// </summary> 10: /// <param name="value"></param> 11: /// <param name="targetType"></param> 12: /// <param name="parameter"></param> 13: /// <param name="culture"></param> 14: /// <returns></returns> 15: public object Convert(object value, 16: Type targetType,
17: object parameter, 18: System.Globalization.CultureInfo culture)
19: {
20: SolidColorBrush result = new SolidColorBrush(Colors.Black); 21:
22: DateTime dt = DateTime.MinValue;
23: if (DateTime.TryParse(value.ToString(), out dt)) 24: {
25: if (dt < DateTime.Now) 26: {
27: result = new SolidColorBrush(Colors.Red); 28: }
29: }
30:
31: return result; 32: }
33:
34: public object ConvertBack(object value, 35: Type targetType,
36: object parameter, 37: System.Globalization.CultureInfo culture)
38: {
39: //We don't need to convert back, so we just return the value. 40: return value; 41: }
42: }
|
Binding to the DataGridTextColumn Foreground property:
If you try to bind your DataGridTextColumn Foreground property to this value converter, you will get an error in runtime which basically means that you cannot bind to the Foreground property.
A quick look in the MSDN documentation: DataGridTextColumn.Foreground will show us that this property is NOT a dependency property. hmmm… why not? we’ll have to ask the Silverlight Team, but we need a solution, so what can we do?
Binding to a DataGridTemplateColumn Foreground:
The fastest solution, is to replace the DataGridTextColumn with a DataGridTemplateColumn. Inside this column we will use a TextBlock which has a Foreground property which IS a dependency property – yeay!
here is the Xaml:
1: <!--
2: PROBLEM:
3: The DataGridTextColumn's Forground property is NOT a dependency 4: propert so we cannot bind our ValueConverter to it.
5:
6: SOLUTION:
7: We use a DataGridTemplateColumn with a TextBlock for the value. 8: Foreground is a Dependency property on TextBlock so binding to a Value Converter works. 9:
10: TIP:
11: You could also create your own DataGridTextColumn (using inheritance) and add your own 12: Dependency property for binding to Foreground... 13: -->
14: <sdk:DataGridTemplateColumn x:Name="DateTime2" Header="Date Time 2" MinWidth="100"> 15: <sdk:DataGridTemplateColumn.CellTemplate>
16: <DataTemplate>
17: <TextBlock Text="{Binding Path=DateTime}" 18: Foreground="{Binding Path=DateTime, 19: Converter={StaticResource dateTimeToColorValueConverter}}"
20: VerticalAlignment="Center" /> 21: </DataTemplate>
22: </sdk:DataGridTemplateColumn.CellTemplate>
23: </sdk:DataGridTemplateColumn>
|
So there we have it. A simple solution to a missing Dependency property.
Tip: You could have inherited DataGridTextColumn and created your own with a Bindable Foreground property – but I will leave that challenge to you, if you have the time.
Thanks for reading, please post any comments you may have.
Jonathan.
WCF RIA Services, DataGrid Filters NO DomainDataSource »
So many of the Silverlight samples use minimal code in an attempt to use controls, binding and commanding techniques to create application by just using markup. Although these techniques are cool and may be great for rapid prototyping of an application, our team prefers to write code that gives us access to the details giving us more control and a better understanding of how things work and how to best use them for our customers.
We write business applications using Silverlight. Many business applications use the Silverlight DataGrid for presenting data. Most DataGrids need to be filtered either automatically or by the user.
We will NOT use the DomainDataSource
Why? Because more often than not, when we used the DomainDataSource, we had to replace it later on, loosing all the time spent on coding and testing in the process.
Tools used?
Download Code: WCFRia_DataGrid_NoDomainDataSource.zip
See How To:
- Bind DataGrid to a WCF RIA Services DomainService method result.
- Pass a client side created Query to a DomainService method to filter the results on the server.
- Create a Filter ComboBox that will use a DISTINCT list of values.
- Use the Filter ComboBox value as a parameter in the client side created query.
Rename the aspx and html test pages from:
to:
then right click index.html and set it as the Default page so it launches when you run/debug the application.
Why? Because index.html and Default.aspx are automatically identified by the web server as the default pages for a web application – one thing less to worry about when you deploy your application.
Add a SampleDatabase to the Web application, App_Data folder.
Double click SampleDatabase.mdf to get the Server Exporer.
Right click on Tables and select New Table
Create a Table called SampleRecord with the following columns and save it.
Why? You may be asking yourself, why the Category field is a string (nvarchar(50)) and not a foreign key to a Category table? The reason is, because we have seen this many times in systems (a non-normalized database) and have found that there may be cases where this is justified. So instead of judging… we decided to show you have we handle such a situation when we encounter it and cannot convince a customer to change it.
Right click the table you create and choose Show Table Data.
Fill the table with some sample data like so:
Add an ADO.NET Entity Data Model to our web project (called SampleDataModel):
Step 1:
Step 2:
Step 3:
Step 4:
Once the model is created, you should see the SampleRecord Entity in your SampleDatamodel.edmx design view.
Build the project to make sure the Entity Model is valid.
Add a Domain Service to our web project (called SampleDomainService):
In the Add New Domain Service Class dialog, remember to:
- Enable Editing
- Check the Generate associated classes for metadata option.
Your web project should look like this:
including:
- SampleDatabase.mdf
- SampleDataModel.edmx
- SampleDomainService.cs
- SampleDomainService.metadata.cs
Now we move to the Silverlight client application project.
Add a new Silverlight UserControl to the Controls folder called FilteredDataGridControl:
In the FilteredDataGridControl do the following:
- Create 2 rows in the Grid (LayoutRoot) control. The top row for our filters and the bottom for our DataGrid.
- Add a StackPanel with a Label and ComboBox to the top row (Grid.Row = “0”)
- Add a DataGrid to the bottom row (Grid.Row = “1”)
It should look like this:
and here is the Xaml inside this UserControl:
1: <Grid x:Name="LayoutRoot" Background="White"> 2: <Grid.RowDefinitions> 3: <RowDefinition Height="25" /> 4: <RowDefinition Height="*" /> 5: </Grid.RowDefinitions> 6: <!-- Filter Area --> 7: <StackPanel Grid.Row="0" Orientation="Horizontal"> 8: <sdk:Label Name="CategoryCaption" 9: Height="23" 10: HorizontalAlignment="Left" 11: VerticalAlignment="Center" 12: Margin="5" 13: MinWidth="120" 14: Content="Category:"/> 15: <ComboBox Name="CategoryFilter" 16: Height="23" 17: HorizontalAlignment="Left" 18: VerticalAlignment="Center" 19: MinWidth="120" /> 20:
21: <Button Name="ApplyFilter" 22: Content="Filter" 23: Height="23" 24: Width="75" 25: Margin="5,0,0,0"/> 26: </StackPanel> 27:
28: <!-- Data Area --> 29: <sdk:DataGrid Name="ResultGrid" 30: Grid.Row="1" 31: AutoGenerateColumns="True" 32: HorizontalAlignment="Stretch" 33: VerticalAlignment="Stretch" 34: Background="Beige"/> 35:
36: </Grid> |
Build the solution.
Add the FilteredDataGridControl onto the Views\Home.xaml page.
1: <my:FilteredDataGridControl x:Name="filteredDataGridControl1" 2: Height="400"/> 3: </StackPanel> 4:
5: </ScrollViewer> 6: </Grid> 7:
8: </navigation:Page> |
Run the application to make sure the control loads and presents correctly
Double click the ApplyFilter button to get to the code behind the form.
Add the following code to the code behind file:
1: using System.ServiceModel.DomainServices.Client; 2: using System.Windows; 3: using System.Windows.Controls; 4: using WCFRia_DataGrid_NoDomainDataSource.Web; 5:
6: namespace WCFRia_DataGrid_NoDomainDataSource.Controls 7: {
8: public partial class FilteredDataGridControl : UserControl 9: {
10:
11: /// <summary> 12: /// Context represents an instance of the SampleDomainContext 13: /// used to access to SampleDomainService methods from the client. 14: /// </summary> 15: private SampleDomainContext context = null; 16: internal SampleDomainContext Context 17: {
18: get { return context; } 19: set { context = value; } 20: }
21:
22: /// <summary> 23: /// Default Contructor 24: /// </summary> 25: public FilteredDataGridControl() 26: {
27: InitializeComponent();
28: this.Loaded += new RoutedEventHandler(OnLoaded); 29: }
30:
31: /// <summary> 32: /// Called after UserControl is loaded 33: /// </summary> 34: /// <param name="sender"></param> 35: /// <param name="e"></param> 36: void OnLoaded(object sender, RoutedEventArgs e) 37: {
38: //new a SampleDomainContext 39: Context = new SampleDomainContext(); 40:
41: BindControls();
42: }
43:
44: /// <summary> 45: /// Set up binding between controls and their data (like Context). 46: /// </summary> 47: private void BindControls() 48: {
49: //Bind ResultGrid's ItemsSource to the context's SampleRecords EntityList 50: ResultGrid.ItemsSource = context.SampleRecords;
51: }
52:
53: /// <summary> 54: /// Called when ApplyFilter Button is clicked. 55: /// </summary> 56: /// <param name="sender"></param> 57: /// <param name="e"></param> 58: private void ApplyFilter_Click(object sender, RoutedEventArgs e) 59: {
60: DoApplyFilter();
61: }
62:
63: /// <summary> 64: /// Construct filter and call DomainService to return filtered results. 65: /// </summary> 66: private void DoApplyFilter() 67: {
68: //Create an EntityQuery object 69: EntityQuery<SampleRecord> query = Context.GetSampleRecordsQuery();
70:
71: //Call Context.Load, pass it a Query and callback function. 72: //This is an Async call. Response will call the OnLoadCompleted 73: //callback function 74: Context.Load(query, OnLoadCompleted, true); 75: }
76:
77: /// <summary> 78: /// Context's Load Completed Callback function. 79: /// </summary> 80: /// <param name="lo"></param> 81: private void OnLoadCompleted(LoadOperation lo) 82: {
83: //Handle errors 84: if (lo.HasError) 85: {
86: MessageBox.Show(lo.Error.Message.ToString());
87: }
88: else 89: {
90: //Data has been loaded into context at this point. 91: }
92: }
93: }
94: }
|
What does this code do?
- We created a local member and property called Context to hold an instance of our SampleDataContext.
SampleDataContext is the client side proxy generated by WCF RIA that we use to call/access the SampleDomainService methods and Entities. - In the Constructor, we subscribe to the Loaded event and in the OnLoaded() method we create a new instance of SampleDataContext and store it in our local Context member. After that we call a method BindControl().
- BindControls() set’s up the binding between our DataGrid (ResultGrid) and the Context.SampleRecords EntityList which represents a list of SampleRecord entities returned by the SampleDomainService.
- DoApplyFilter() is a method called from the ApplyFilter_Click button click event handler. DoApplyFilter() contructs an EntityQuery and then calls the Context.Load method.
- Context.Load(…) makes an Asynchronous call to our SampleDomainService. We pass it our EntityQuery and a callback function OnLoadCompleted().
- OnLoadCompleted() will be called when the SampleDomainService call completes (or times out) and if it was successful it will update the data in our SampleDataContext (local Context member) with the data returned.
DomainContext – our disconnected client side data store
It is important to understand the role of the DomainContext (or in our case SampleDataContext). This is a WCF RIA generated class that serves as a client proxy for calling the DomainService methods, but also serves as a client side data store (kind of like a disconnected DataSet) where data is collected when ever you load add/edit or delete entities.
One important thing to be aware of is that if you call Load on an Entity, every load will just Add/Append any returned records to the DataContext’s EntityList. If this is not what you want, you need to clear the relevant EntityList before calling a new load operation.
Binding a ComboBox to a DISTINCT list of Categories
In our sample, we need to get a distinct list of Category values to serve as the values for our FilterComboBox control. In SQL this would be something like SELECT DISCTINCT Category FROM SampleRecord. So how do we give our ComboBox a list of Distinct Category Values to bind to using WCF RIA Services?
First, we will create a method in our DomainService to get a list of Categories.
Tip: We have found that instead of adding custom methods to the generated SampleDomainService.cs class which would get overwritten if you had to create it again after adding or changing tables, we change it to be a partial class and create our own SampleDomainService.custom.cs class with our custom methods like so:
Add a new [Invoke] method to SampleDomainService.custom.cs:
1: public partial class SampleDomainService 2: {
3: /// <summary> 4: /// Get's a Distinct list of Categories from the SampleRecord Table. 5: /// </summary> 6: /// <returns></returns> 7: [Invoke]
8: public List<string> FillCategoryList() 9: {
10:
11: return (from r in ObjectContext.SampleRecords 12: select r.Category).Distinct().ToList();
13:
14: }
15: }
|
Notice that this is a partial class (you need to make SampleDomainService.cs a partial class also) and the method is decorated with the [Invoke] attribute. The reason we made this an [Invoke] method is because it does NOT return an Entity and Query methods must return Entities. The method returns a List (System.Collections.Generics) of string.
Build your solution to regenerate the client side SampleDomainContext with this new method.
Now on the client, add an ObservableCollection that will be used as our CategoryFilter ItemsSource:
1: /// <summary> 2: /// Collection used as ItemsSource for CategoryFilter ComboBox 3: /// </summary> 4: private ObservableCollection<string> categoryFilterList; 5: public ObservableCollection<string> CategoryFilterList 6: {
7: get { return categoryFilterList; } 8: set { categoryFilterList = value; } 9: }
|
and add the DoPopulateFilter() method:
1: /// <summary> 2: /// Calls DomainService, gets a Distinct List of Categories 3: /// from SampleRecord.Category and Binds the result 4: /// to the CategoryFilter ComboBox. 5: /// </summary> 6: private void DoPopulateFilter() 7: {
8: //Call Invoke Method to get a list of distinct categories 9: InvokeOperation<IEnumerable<string>> invokeOp = Context.FillCategoryList(); 10: invokeOp.Completed += (s, e) =>
11: {
12: if (invokeOp.HasError) 13: {
14: MessageBox.Show("Failed to Load Category Filter"); 15: }
16: else 17: {
18: //Populate Filter DataSource 19: CategoryFilterList = new ObservableCollection<string>(invokeOp.Value); 20:
21: //Add a Default "[Select]" value 22: CategoryFilterList.Insert(0, "[Select]"); 23:
24: CategoryFilter.ItemsSource = CategoryFilterList;
25: }
26: };
27: }
|
What does this method do?
9: Creates a new InvokeOperation with our new Method “FillCategoryList()”.
10: Is an inline delegate/callback for getting the results of the method call (you could have created a separate OnCompleted… method for this, we just wanted to show you this format which is sometimes useful and makes the code more readable.
18 – 24: We create a new ObservableCollection using the result of the method call (List<string>) in its constructor, then adding a dummy “[Select]” value (although this should probably be done on the server) and then set the resulting ObservableCollection as our CategoryFilter ComboBox’s ItemsSource.
In FilteredDataGridConrol.cs add a call to DoPopulateFilter() in the Onloaded() method:
1: void OnLoaded(object sender, RoutedEventArgs e) 2: {
3: //new a SampleDomainContext 4: Context = new SampleDomainContext(); 5:
6: BindControls();
7:
8: DoPopulateFilter();
9: }
|
Now, if you run the application, you will see that the category list is loaded into the ComboBox (CategoryFilter).
Adding our Filters to the query on the client side:
We want to construct a query based on the criteria selected by the user, send that query via WCF RIA Services to get the result. This is cool, because we don’t need to change our service method signatures every time we need to add additional criteria, we just pass a query object.
This is done in the following method:
1: /// <summary> 2: /// Construct filter and call DomainService to return filtered results. 3: /// </summary> 4: private void DoApplyFilter() 5: {
6: //Create an EntityQuery object 7: EntityQuery<SampleRecord> query = Context.GetSampleRecordsQuery();
8:
9: //Get user selected Filter Criteria 10: if (null != CategoryFilter.SelectedValue) 11: {
12: string filterCategory = CategoryFilter.SelectedValue.ToString(); 13: if (!string.IsNullOrWhiteSpace(filterCategory) 14: && filterCategory != "[Select]") 15: {
16: query = query.Where(s => s.Category == filterCategory);
17: }
18: }
19:
20:
21: //Clear Context so we get new results 22: Context.SampleRecords.Clear();
23:
24: //Call Context.Load, pass it a Query and callback function. 25: //This is an Async call. Response will call the OnLoadCompleted 26: //callback function 27: Context.Load(query, OnLoadCompleted, true); 28: }
|
Line 16: is interested. This format actually adds (concatenates) additional WHERE conditions to the query object so you could do this for each of the criteria options provided by your user interface.
Now, when you run the application and select a specific category you will notice that the data is filtered accordingly.
Hopefully some of the techniques shown here will help you get started or complete a task.
Please send us your comments so we can learn from you or answers any questions you may have.
Jonathan
Apparently not crazy – business is booming… »
It’s been a while, but that’s only because we have been crazy busy.
Working with our first few customers, Silverlight 2 then 3 then 4. New additions to our families – working hard and playing hard!
I think we can answer our own question “Is it crazy to start a company in this economy?” – and the answer is NO its not crazy it was perfect timing!
Why?
- Because it was a time when many highly experienced friends and colleagues were thinking on how to protect themselves from the economy in the future – allowing us to partner with great people looking to build a strong company building great solutions.
- Customers were looking for ways to bring their applications to the cloud – Silverlight’s advantages in deployment and multiplatform support, made it an easy sell.
- Companies wanted results with minimal investment, so it was easier for them to hire us than consider long training programs and risky projects on a new technology they were not ready for.
So business is good. We are not rich yet, but offers are flowing in, results are being produced and we are doing what we love doing – BUIDLING SOFTWARE.
So if you are thinking of starting a business, by all means GO FOR IT!
The Net Us Up, Team.
Is it crazy to start a company in this economy? »
Hi,
A few friends and I decided that we wanted to start a company. The first thoughts that went through our heads were “is this a good time to start a business?” and the answer was yes!
We have all thought about creating our own company, during the many days we have worked hard for others to make other people’s companies grow, but it never happened. Well, the bad economy, people getting fired, and the uncertainty of placing our future in the hands of others was the tipping point.
So we are now the proud founders of Net-Us-Up. We have a set of problems we know how to solve and are working on packaging them in a modern, pay-as-you-go (software as a service, cloud offering or what ever the current acronyms are) package and get users to pay us for our services.
It is exciting, and after only one month of working on this part time, we already have a working prototype of our first offering which is great!
So if you are thinking about it. Just go ahead and do it. Keep your day job, sleep a little less and let your family know that you are going to be busy with a new venture and just give it your all.
Stay tuned, there is more to come.
J.
0
-