Wednesday, December 29, 2010

Waiting for a Save to Complete in an Async Manner

Silverlight is by its design an asynchronous environment.  But sometimes, you really just need to wait for an action to complete before letting the user move on to any other operations.  Ideally, you want to do this in a manner that does not lock the UI thread so that the app is still responsive.  

The best way to do this is to have the method that is doing the action raise an event when the event is complete.  To do this, first declare the event that will be raised, along with any event arguments you want to include with it.

           #region "Events"  
public class SaveCompletedEventArgs : System.EventArgs
{
public bool SaveSuccessful { get; set; }
}
public event System.EventHandler<SaveCompletedEventArgs> SaveCompleted;
#endregion

Then when the Save is completed raise the event (SaveCompleted in this case).

 _Context.SubmitChanges(  
(submitOperation) =>
{
saveSuccessful = false;
IsBusy = false;
if (!submitOperation.HasError)
saveSuccessful = true;
SaveCompleted(this, new SaveCompletedEventArgs() { SaveSuccessful = saveSuccessful });
},null);

Note that in the code above, when the SubmitChanges is run, the program will continue with the next line and return control to the caller.


In the caller, you would do something like the following to handle the event. Use VS2010 to automatically create the event handler for you.

 _ViewModel.SaveCompleted += new EventHandler<ViewModels.AddShoppingListViewModel.SaveCompletedEventArgs>(_ViewModel_SaveCompleted);  

Monday, November 22, 2010

Divide by Zero Error in ReportingServices

Occasionally you need to do division in a ReportingServices report, for example where you have to calculate percentages.  But when your divisor is zero you get an ugly ‘#Error’ in the field where the division is done. 

Checking for a zero in the divisor seems like an obvious solution, but unfortunately the ‘IIF’ statement that you would use to check for this evaluates the True and False parts of the answer regardless of the result of the result it is evaluating.   So if Y = 0, and your field has the following: IIF(Y=0, 0, X/Y), you will still get the #Error message. 

The fix for this is to put the following code in the report:

  Public Function Divide(ByVal first As Double, ByVal second As Double) As Double  
If second = 0 Then
Return 0
Else
Return first / second
End If
End Function

Then in the field replace the IIF statement with the following Code.Divide (X/Y).


That should take care of it.

Friday, July 30, 2010

Outlook 2010/IMAP/Google Apps

With Outlook 2010 over IMAP to Google Apps/Gmail, the default setting is that when you delete an item, it is moved into ‘Trash’ instead of archived. This is a problem if you count on having those Archived items out there for future reference. (Trash is discarded after 30 days).

To change this:

  • Click on File/Account Settings
  • Doubleclick the e-mail account you want to change.
  • Click More Settings
  • Go to the Delete Items Tab
  • Select '”Move deleted items to the following folder”
  • Select the “All Mail” folder in the Tree View. image
  • Click OK
  • Click Next and let it finish the setup.

That should do it. Now all deleted items will be Archived.

Thursday, July 8, 2010

Restoring a Windows State When Navigating Back to a Page

When navigating to another page and back again using the Silverlight Navigation Framework, sometimes it is useful to return to the page and see it exactly as it was.  Fortunately, this is easy to do if you are using an MVVM framework. 

When navigating away from the page, save the ViewModel in the App.Current.Resources collection.


protected override void OnNavigatedFrom(NavigationEventArgs e)
{
if (App.Current.Resources["theViewModel"] != null)
App.Current.Resources.Remove("theViewModel");
App.Current.Resources.Add("theViewModel", this.DataContext);
base.OnNavigatedFrom(e);
}

When returning to the page, pull it back out again and assign it to the page.


         protected override void OnNavigatedTo(NavigationEventArgs e)  
{
if (App.Current.Resources["theViewModel"] != null)
this.DataContext = App.Current.Resources["theViewModel"];
}

Thursday, June 24, 2010

Syntax for programmatically retrieving a recordset from RIAServices

         private ObservableCollection<MyEntity> _MyEntityList;  
public ObservableCollection<MyEntity> MyEntityList
{
get{ return _MyEntityList; }
set
{
_MyEntityList = value;
RaisePropertyChanged("MyEntityList");
}
}

public void LoadData()
{
_Context.Load(_Context.GetMyEntityQuery(), LoadBehavior.RefreshCurrent,
DataLoaded, null);
IsBusy = false;
}

public void DataLoaded(LoadOperation<MyEntity> loadOperation)
{
if (loadOperation.HasError)
{
System.Windows.MessageBox.Show(loadOperation.Error.ToString(), "Load Error", System.Windows.MessageBoxButton.OK);
loadOperation.MarkErrorAsHandled();
MyEntityList = null;
}
else
MyEntityList = new ObservableCollection<MyEntity>(loadOperation.Entities);
IsBusy = false;
}

Sunday, June 6, 2010

Increasing the size of a VHD

Increasing the size of a VHD file is done in two steps.
Step 1 is to increase the size of the file itself. That can be done with the VBoxManage utility found in the c:\Program Files\Oracle\VirtualBox directory.  Run the following:
VBoxManage modifyhd YOUR_HARD_DISK.vdi --resize SIZE_IN_MB
Step 2 is change the partition to take up the extra space created in Step 1. That can be done with the DiskPart tool that is built into Windows. To do this,  Launch the virtual machine, go to a command prompt (Administrator Mode), and run:
diskpart
Get a list of the volumes mounted on the machine:
list volume
From the resulting list, get the number of the volume for the one you want to extend. Select the volume:
select volume x
Where x is the # you got from the list volume command. Verify you have selected the right volume:
detail volume
Extend the partition to take all available space:
extend
Shut down the virtual machine, disconnect the volume, and it should be ready to mount to the original virtual pc.

Friday, May 28, 2010

Using Silverlight Isolated Storage

To store string data in Isolated Storage:

IsolatedStorageSettings.ApplicationSettings.Add("KeyName",
    Value);

To remove:

if (IsolatedStorageSettings.ApplicationSettings.Contains("KeyName"))
    IsolatedStorageSettings.ApplicationSettings.Remove("KeyName");

Invoking a Method Call Over RIA Services

To invoke a method through RIA Services:

On the server side, declare the method, an decorate it with the [Invoke] annotation. Return types can be simple types or collections.  Not sure if complex types work or not.

[Invoke]

public string BuildAdjusterMetricBatch(string users)
{
    DoSomeWork()

    return batch;
}

On the client side, invoke the method, and pass it a function for the asynchronous Callback:

var context = new Web.Services.MSIClaimContext();
context.BuildAdjusterMetricBatch(txtFilter.Text,  
    BatchBuildCompleted, null);

then declare a method for the callback that matches the return type of the Method.

void BatchBuildCompleted(InvokeOperation<string>
    invokeOperation)
{
    string myValue = invokeOperation.Value;
    DoClientSideWork();

}

Tuesday, May 25, 2010

Column Headers in Reporting Services 2008

If the column headers in Reporting Services 2008 won't repeat on a new page, do the following.

  • Click on the triangle button at the bottom of the screen, and click on "Advanced Mode".
  • In the "Row Groups" section of the window, click on the "Static" element at the top of the list.
  • Set the following properties:
    • FixedData = True
    • KeepWithGroup=After
    • RepeatOnNewPage=True

image

Load operation failed for query ‘GetUser’

Getting this error when deploying a Silverlight RIAServices application to a new server.

Fiddler helped debug this one.   Pulling up the Response header in Fiddler gave the following additional information:

IIS specified authentication schemes 'IntegratedWindowsAuthentication, Anonymous', but the binding only supports specification of exactly one authentication scheme. Valid authentication schemes are Digest, Negotiate, NTLM, Basic, or Anonymous. Change the IIS settings so that only a single authentication scheme is used

Had to reboot the server after making the change, but this resolved the problem.

Sunday, May 23, 2010

Calling a RIAServices Query from Code

var context = Project.Web.Services.DomainContext();

context.Load(
    context.GetSomeItemQuery(parameters),
    LoadBehavior.RefreshCurrent, DataLoaded, null);

void DataLoaded(LoadOperation<Web.Models.Entity> loadOperation)
{
    loadOperation.Entities = …

DependencyProperty

Sometimes it is necessary to bind to a custom property on a Silverlight Control.  In order for that to work properly, you need to set it up as a DependencyProperty. 

public int Denominator
{
    get { return (int)this.GetValue(DenominatorProperty); }
    set { this.SetValue(DenominatorProperty, value); }
}

public static readonly DependencyProperty DenominatorProperty
    = DependencyProperty.Register(
            "Denominator", //Property Name
            typeof(int), //Property Type
            typeof(QualitativeStatusIndicator),  // type of Control
            new PropertyMetadata(
                 0, // default value
                 Denominator_PropertyChangedCallback));  // callback

private static void Denominator_PropertyChangedCallback(
    DependencyObject d, DependencyPropertyChangedEventArgs e)
{
    QualitativeStatusIndicator myControl
       = d as QualitativeStatusIndicator;
    myControl.SetStatus();
}

Friday, May 21, 2010

Adding and Deleting from a RIAServices DomainDataSource

If you drag and drop a ‘Details’ form from the DataSources window onto a Silverlight form, by default you won’t have any add or save ability.  To fix this, you have to add buttons to the form to do the Insert and Delete.  The code behind for those buttons looks like this:

private void btnAdd_Click(object sender, RoutedEventArgs e)
{
    Web.Models.UserEdit newUser = new Web.Models.UserEdit();
    userEditDomainDataSource.DataView.Add(newUser);
}
private void btnDelete_Click(object sender, RoutedEventArgs e)
{
    userEditDomainDataSource.DataView.Remove(userEditDomainDataSource.DataView.CurrentItem);
}

Thursday, May 20, 2010

Converting One Kind of Collection to Another

Getting an ObservableCollection from an IEnumerable (this is the type of loadOperation.Entities

myCollection = new ObservableCollection<myEntityType>(loadOperation.Entities)

// DON’T do this. It returns a null value

//myCollection = (ObservableCollection<myEntityType>)
//loadOperation.Entities;

Converting an iQueryable returned by EntityFramework into a List.  Note that this is an extension method, which means you must be “Using System.Linq” in order have this function available to the iEnumerable.

var myQueryable = (from x in myContext.table select x).ToList()

Friday, April 9, 2010

Using SQL to Generate XML

To create this XML block:

<EventArguments>
<OccurrenceId>300031738</OccurrenceId>
<HoursInProcess>123</HoursInProcess>
<ContactName>McCusker &amp; Ogborne &lt;Other&gt;</ContactName>
<AccidentFormId>795</AccidentFormId>
<BusinessUnitNumber>*</BusinessUnitNumber>
</EventArguments>

You would do this:

SELECT '300031738' AS OccurrenceId,
123 AS HoursInProcess,
'McCusker & Ogborne <Other>' AS ContactName,
795 AS AccidentFormId,
'*' AS BusinessUnitNumber,
FOR XML RAW('EventArguments'), ELEMENTS

-

For more sophisticated XML, you can do sub queries. This may not make sense to include this particular table, but it shows how it would work.

SELECT '300031738' AS OccurrenceId,
123 AS HoursInProcess,
'McCusker & Ogborne <Other>' AS ContactName,
795 AS AccidentFormId,
'*' AS BusinessUnitNumber,
(SELECT TOP 5 ProcessedMessageLogId FROM ProcessedMessageLog FOR XML RAW(''), ELEMENTS, TYPE) AS Children
FOR XML RAW('EventArguments'), ELEMENTS

- Which creates the following XML:

<EventArguments>
<OccurrenceId>300031738</OccurrenceId>
<HoursInProcess>123</HoursInProcess>
<ContactName>McCusker &amp; Ogborne &lt;Other&gt;</ContactName>
<AccidentFormId>795</AccidentFormId>
<BusinessUnitNumber>*</BusinessUnitNumber>
<Children>
<ProcessedMessageLogId>3281</ProcessedMessageLogId>
<ProcessedMessageLogId>3282</ProcessedMessageLogId>
<ProcessedMessageLogId>3283</ProcessedMessageLogId>
<ProcessedMessageLogId>3284</ProcessedMessageLogId>
<ProcessedMessageLogId>3285</ProcessedMessageLogId>
</Children>
</EventArguments>

Thursday, April 8, 2010

Confirming Delete in a GridView

I tried a bunch of ways but this was the only way to get this to work reliably. The problem is that the ButtonFields in the GridView don’t have an OnClientClick event the way that every other button does. To remedy this, you have to attach the ClientClick handler when the grid is being generated.

So in your Columns collection of the GridView, you will have a button field like this:

<asp:ButtonField ButtonType="Link" CommandName="Clear" Text="Clear" />

Then in the code behind you need to do this:

protected void grdCourses_RowDataBound(object sender , GridViewRowEventArgs e)
{
if (e.Row.RowType != DataControlRowType.DataRow) return;

int lastCellIndex = e.Row.Cells.Count - 1;
LinkButton clearButton = (LinkButton)e.Row.Cells[lastCellIndex].Controls[0];
clearButton.OnClientClick =
"if (!window.confirm('Are you sure you want to clear this score?')) return false;";
}

Wednesday, February 24, 2010

Concatenating Strings Across Records In SQL Server

Occasionally it is necessary to concatenate strings from a number of different records and return them as a single value in another query.   For example, in the classic invoice/detail scenario, maybe you want a list of invoices, and with each invoice, a comma delimited list of all the descriptions of each detail record.  But this detail information has to be returned as a single text field as part of the invoice.  
 
To do this you need to create a function:
 
IF EXISTS (SELECT * FROM sysobjects WHERE name = 'DetailList')
BEGIN
  DROP  FUNCTION DetailList
END
GO
CREATE FUNCTION DetailList@InvoiceId INT) RETURNS varchar(max)
BEGIN
DECLARE @Build varchar(max)
SELECT @Build=''
SELECT @Build = @Build + Description +  ', '
  FROM Detail
WHERE Detail.InvoiceId = @InvoiceId
RETURN @Build
END
GO
 
 
When calling this function you return it as if it were a field in the invoice table.   Don't forget to prefix it with "dbo."
select Amount, dbo.
 
 
 
 

Wednesday, February 10, 2010

Silverlight Non-Descript Errors When Deploying Using WCF Services

You deliver a Silverlight module that uses WCF Services to a production server.  It works great on your machine, but when you run it in production, you get an error every time you call out to a WCF service.  
 
The issue may be in the ServiceReference.Config file.  In the <System.serviceModel> section, in the <client> section, you will see endpoints for every service.  Change the 'address' of these to the endpoints that you use in production, recompile, and deliver the module.