Thursday, September 24, 2009

Error management on a windows forms application form

I've been working on this as to help keep my error status straight on my windows form


//begin EPM
        private class ErrorProviderManager
        {
            private ErrorProvider _ep;
            Dictionary<Control, List<KeyValuePair<Func<bool>, String>>> dic =
                new Dictionary<Control, List<KeyValuePair<Func<bool>, String>>>();
            Dictionary<Control, List<String>> dicPossibleErrorStrings = new Dictionary<Control, List<String>>();
            List<Control> verifyWithErrorProviderList = new List<Control>();

            public ErrorProviderManager(ErrorProvider ep)
            {
                _ep = ep;
            }
            public bool hasErrors()
            {
                var result = false;
                foreach (var item in dic.Keys)
                {

                    var itemHasErrors = hasErrors(item);
                    if (itemHasErrors) result = true;
                }
                foreach (var item in verifyWithErrorProviderList)
                {
                    if (_ep.GetError(item).IsNullOrEmpty() == false)
                        result = true;
                }
                return result;
            }

            public bool hasErrors(Control item)
            {


                var itemHasErrors = false;
                if (_ep.GetError(item).IsNullOrEmpty() == false && dicPossibleErrorStrings[item].Contains(_ep.GetError(item)) == false)
                    itemHasErrors = true;
                else
                {
                    foreach (var kvp in dic[item])
                    {
                        if (itemHasErrors == false && kvp.Key.Invoke())
                        {
                            _ep.SetError(item, kvp.Value);
                            itemHasErrors = true;
                            break;
                        }

                    }

                    if (itemHasErrors == false)
                        _ep.SetError(item, string.Empty);
                }
                return itemHasErrors;
            }

            public void AddErrorCondition(Control c, Func<bool> errorCondition, String errorMessage)
            {
                if (errorMessage.IsNullOrWhitespace() == false)
                {
                    if (verifyWithErrorProviderList.Contains(c))
                        verifyWithErrorProviderList.Remove(c);
                    if (dic.ContainsKey(c) == false)
                        dic.Add(c, new List<KeyValuePair<Func<bool>, string>>());
                    dic[c].Add(new KeyValuePair<Func<bool>, String>(errorCondition, errorMessage));
                    if (dicPossibleErrorStrings.ContainsKey(c) == false)
                        dicPossibleErrorStrings.Add(c, new List<string>());
                    dicPossibleErrorStrings[c].Add(errorMessage);
                }
            }
            public void AddCheckForCustomErrorProviderText(Control c)
            {
                if (dicPossibleErrorStrings.ContainsKey(c) == false && verifyWithErrorProviderList.Contains(c) == false)
                    verifyWithErrorProviderList.Add(c);
            }

            private void _ClearError(Control c)
            {
                _ep.SetError(c, string.Empty);
            }

            public void refreshErrors()
            {
                hasErrors();
            }

        }
        //end EPM

Thursday, September 17, 2009

DRY Documentation

Excellent information from "The Productive Programmer":


Note: Out-of-date documentation is worse than none because it is actively misleading.

Documentation is a classic battleground between management and developers: managers want more and developers want to create less. It is also a battleground in the war against noncanonical representations. Developers should be able to make changes to code aggressively, to improve its structure and allow it to evolve. If you must have documentation for all your code, it must evolve at the same time. But most of the time, they get out of sync because of schedule pressure, lack of motivation (because , let's face it, writing code is more fun than writing documentation), and other factors.
Note: for managers documentation is about risk mitigation.

Out-of-date documentation  creates the risk of spreading misinformation (which is ironic, given that part of its purpose is to reduce risk).
  - Neal Ford

Tuesday, September 15, 2009

buffering linq changes for row at a time submits

So it appears linq-to-sql objects do not support anything to cancel changes for an individual row. Here's the parent class I'm using on my business object wrapper between the ui, and my linq entities:
public class BufferedLinqChange:System.ComponentModel.IEditableObject
{
LqGpsDataContext _dataContext;
 
internal BufferedLinqChange(LqGpsDataContext dataContext)
{
_dataContext = dataContext;
}
 
protected void SetBufferedProperty<T>(string key,Action linqAction
,bool linqEqualsValue,Action bufferAction)
{
if (linqEqualsValue)
{
if (Changes.ContainsKey(key))
Changes.Remove(key);
}
else
Changes.InsertOrUpdate(key, linqAction); bufferAction();
}
 
protected Dictionary<String, Action> Changes = new Dictionary<string, Action>();
 
public int ChangeCount { get { return Changes != null ? Changes.Count : 0; } }
public bool hasChanges { get { return Changes != null ? Changes.Count > 0 : false; } }
 
/// <summary>
/// Learned about this from http://www.vbforums.com/showthread.php?t=584096
/// Other sources:
/// Is IEditableObject too hard?
/// http://go.internet.com/?id=474X1146&url=http%3A%2F%2Fwww.madprops.org%2Fblog%2Fis-ieditableobject-too-hard%2F
/// </summary>
#region IEditableObject Members
 
public void BeginEdit()
{
}
 
public void CancelEdit()
{
if (Changes != null)
Changes.Clear();
}
 
public void EndEdit()
{
_dataContext.SubmitChanges();
if (ChangeCount > 0)
{
Changes.ForEach((item) => item.Value.Invoke());
_dataContext.SubmitChanges();
}
}
 
#endregion
}
A sample property in snippet form would be implemented like this:

#region $propertyName$
 
 
$fieldType$_$propertyName$;
public const String STR_$propertyName$ = "$propertyName$";
public $fieldType$ $propertyName$
{
get { return (Changes.ContainsKey(STR_$propertyName$) ? _$propertyName$ : $entityHolder$.$propertyName$); }
set
{
SetBufferedProperty<$fieldType$>(STR_$propertyName$
, () => $entityHolder$.$propertyName$ = value, $entityHolder$.$propertyName$ == value, () => _$propertyName$ = value);
}
}
#endregion

I'll probably go back and make bufferedLinqChange generic so it can hold and require the linq entity type to be defined. But you'll find since the business object now supports IEditableObject, moving to a different record when it is in a bindingSource, automatically starts the beginEdit, and calls endEdit. I overrode that behavior so that a prompt is given to the user to decide to save or not before navigating. The before navigation code is as follows:

private void ConditionalMove(Action OnMoveSuccess)
{
this.Validate();
Action _OnMoveSuccess = () => { OnMoveSuccess();
//    itemChanges = false; 
};
var current = this.bnAssets.BindingSource.Current.DirectCast<ModelAsset>();
if (_epm.hasErrors() || current.hasChanges) //|| itemChanges)
switch ("Unsaved changes exist save?".ToMessageBox("Save changes", MessageBoxButtons.YesNoCancel))
{
case DialogResult.Yes:
this.bnAssets.BindingSource.EndEdit();
var saveSuccess = false;
try
{
this.dc.dc.SubmitChanges();
saveSuccess = true;
}
catch (Exception exception)
{
exception.Message.ToMessageBox();
}
if (saveSuccess) _OnMoveSuccess();
break;
case DialogResult.No:
current.CancelEdit();
this.errorProvider.Clear();
_OnMoveSuccess();
break;
case DialogResult.Cancel:
 
default:
break;
}
else
_OnMoveSuccess();
}

Then a navigation button would point to:

private void bindingNavigatorMoveNextItem_Click(object sender, EventArgs e)
{
ConditionalMove(() => this.bnAssets.BindingSource.MoveNext());
}

Monday, September 7, 2009

DataTimePicker databinding vs nullable DateTime

I just came across an interesting problem. You can't bind a nullable dateTime directly to a dateTimePicker. At least not easily. Here's an extension method that handles the heavy lifting.


/// <summary>
/// 
/// </summary>
/// <param name="dtp"></param>
/// <param name="dataSource"></param>
/// <param name="valueMember"></param>
/// <remarks>With help from Dan Hanan at http://blogs.interknowlogy.com/danhanan/archive/2007/01/21/10847.aspx</remarks>
public static void BindNullableValue(this DateTimePicker dateTimePicker, BindingSource dataSource, String valueMember)
{
var binding = new Binding("Value", dataSource, valueMember, true);
//OBJECT PROPERTY --> CONTROL VALUE
binding.Format += new ConvertEventHandler((sender, e) =>
{
Binding b = sender as Binding;
if (b != null)
{
DateTimePicker dtp = (binding.Control as DateTimePicker);
if (dtp != null)
{
if (e.Value == null)
{
dtp.ShowCheckBox = true;
dtp.Checked = false;
 
// have to set e.Value to SOMETHING, since it's coming in as NULL
// if i set to DateTime.Today, and that's DIFFERENT than the control's current
// value, then it triggers a CHANGE to the value, which CHECKS the box (not ok)
// the trick - set e.Value to whatever value the control currently has. 
// This does NOT cause a CHANGE, and the checkbox stays OFF.
 
e.Value = dtp.Value;
 
}
else
{
dtp.ShowCheckBox = true;
dtp.Checked = true;
// leave e.Value unchanged - it's not null, so the DTP is fine with it.
}
 
}
 
}
});
// CONTROL VALUE --> OBJECT PROPERTY
binding.Parse += new ConvertEventHandler((sender, e) => {
// e.value is the formatted value coming from the control. 
// we change it to be the value we want to stuff in the object.
Binding b = sender as Binding;
 
if (b != null)
{
DateTimePicker dtp = (b.Control as DateTimePicker);
if (dtp != null)
{
if (dtp.Checked == false)
{
dtp.ShowCheckBox = true;
dtp.Checked = false;
e.Value = (Nullable<DateTime>) null;
}
else
{
DateTime val = Convert.ToDateTime(e.Value);
e.Value =val;
}
}
}
});
dateTimePicker.DataBindings.Add(binding);
 
}

Wednesday, July 1, 2009

Silverlight dataGrid + grid issue

So it seems you can't use a dataset, datatable, datarow, or nearly anything else to send silverlight controls data. The only thing it did allow was LINQ to SQL.  Either way I finally got a data display going.
 Here's the Xaml.
[code]
1: <UserControl xmlns:data="clr-namespace:System.Windows.Controls;assembly=System.Windows.Controls.Data" x:Class="SilverlightApplication1.Page" 
2:  xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
3:  xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 
4:  Width="500" Height="300"> 
5:  <!--If you don't put a Grid or some other container on your control you wind up only being able to display one 'thing'--> 
6:  <Grid x:Name="LayoutRoot" Background="White" ShowGridLines="True"> 
7: <!--If you don't give it row definitions it wound up only showing a single 'thing' in the grid--> 
8:  <Grid.RowDefinitions> 
9:  <RowDefinition Height="8*" /> <!--The star is for proportional sizing--> 
10:  <RowDefinition Height="2*" /> 
11:  </Grid.RowDefinitions> 
12:  
13:  <data:DataGrid x:Name="dgBLog" Grid.Row="0" /> <!-- for some reason it did not let me define a row and stick a dataGrid in it--> 
14:  <TextBox x:Name="lblStatus" Text="Starting" Grid.Row="1" /> <!-- used to show debug/status information --> 
15:   
16:  </Grid> 
17:  
18: </UserControl> 
[/code]
 
 
Then here's the Page.xaml.cs


 
namespace SilverlightApplication1
{
public partial class Page : UserControl
{
public Page()
{
InitializeComponent();
Loaded += new RoutedEventHandler(Page_Loaded);
}
 
void Page_Loaded(object sender, RoutedEventArgs e)
{
 
ServiceReference1.ServiceHomeqClient webService = new ServiceReference1.ServiceHomeqClient();
 
lblStatus.Text = "Loaded";
webService.BLogCountCompleted += new EventHandler<SilverlightApplication1.ServiceReference1.BLogCountCompletedEventArgs>(webService_BLogCountCompleted);
webService.BLogCountAsync();
webService.getErrorsCompleted += new EventHandler<SilverlightApplication1.ServiceReference1.getErrorsCompletedEventArgs>(webService_getErrorsCompleted);
webService.getErrorsAsync();
}
 
void webService_getErrorsCompleted(object sender, SilverlightApplication1.ServiceReference1.getErrorsCompletedEventArgs e)
{
this.dgBLog.ItemsSource = e.Result;
DisplayDiag();
}
 
 
 
void DisplayDiag()
{
var diag = new System.Text.StringBuilder();
diag.AppendLine("Finished retrieval");
 
this.lblStatus.Text = diag.ToString();
}
 
 
}
}
 
Here's the service code that feeds silverlight from a seperate asp.net project. It makes use of a Linq-to-sql class lqHomeQ.


using System;
using System.Linq;
using System.Runtime.Serialization;
using System.ServiceModel;
using System.ServiceModel.Activation;
using System.Collections.Generic;
using System.Text;
 
namespace SilverlightApplication1.Web
{
[ServiceContract(Namespace = "")]
[AspNetCompatibilityRequirements(RequirementsMode = AspNetCompatibilityRequirementsMode.Allowed)]
public class ServiceHomeq
{
lqHomeQDataContext db = new lqHomeQDataContext();
[OperationContract]
public void DoWork()
{
// Add your operation implementation here
return;
}
[OperationContract]
int BLogCount()
{
return db.bLogs.Count();
}
[OperationContract]
IEnumerable<bLog> getTodaysBLog()
{
//return db.bLogs.Where(row => row.dt.Date == DateTime.Today).ToList(); // table is larger than I'd like for this method
 
var result = from b in db.bLogs.Take(2)
select b;
return result.ToList();
}
[OperationContract]
IEnumerable<rejMtError> getErrors()
{
var result = db.rejMtErrors;
return result.ToList();
}
// Add more operations here and mark them with [OperationContract]
}
} 
 
  I based this off of this example
  from c-sharpcorner.com