Monday, June 2, 2014

Pitfalls of using WPF DataGrid

WPF DataGrid is a very powerful Control that was first published within the WPF Toolkit. Since .NET 4.0 the WPF DataGrid is part of the .NET Framework. The simplest way to use it, is to set the ItemsSource, only.
<DataGrid ItemsSource="{Binding Source1}" />
public ICollection<IValue> Source1 { getset; }
Due to the AutoGenerateColumns property, which default value is true, the DataGrid is created with the properties of the class that is used within the collection. In order that this works
  • the binded collection must be public
  • the properties of the used class must be public
For example the interface could look like that
public interface IValue
{
    string Val { get; }
    string Key { get; }
}
and the implementation of the interface could look like that
public class KeyVal : IValue
{
    public KeyVal(string key, string val)
    {
        Key = key;
        Val = val;
    }
 
    public string Val
    {
        get;
        private set;
    }
 
    public string Key
    {
        get;
        private set;
    }
}
All that results into

As you can see columns are auto-generated and filled with the objects of the binded list.
For just viewing collections this is all you have to consider. If you want also add new entries to the collection there must be done some changes. In the XAML part the CanUserAddRows property indicates if it is possible to add new entries. The default value is true, so the XAML part remains unchanged. In code we have to do some changes
  • the generic type of the collection must be a class (not an interface)
  • there must be a default constructor (parameterless) in the generic type of the collection
public ICollection<KeyVal> Source1 { getset; }
public class KeyVal : IValue
{
    public KeyVal()
    {
 
    }
 
    public KeyVal(string key, string val)
    {
        Key = key;
        Val = val;
    }
 
    public string Val
    {
        get;
        set;
    }
 
    public string Key
    {
        get;
        set;
    }
}
Only with these changes a new object can be created from the view and added to the collection. As result there is a new empty entry in the DataGrid that can be filled by the user and is then added to the collection.

Friday, May 30, 2014

Prevent System.Windows.Data Error in Output window while evaluating Validation.Errors in XAML


Validation of UI input parameters in WPF is a frequently requirement. There are several approaches to realize an input validation. You can use custom ValidationRule, ExceptionValidationRule or IDataErrorInfo. Excellent descriptions how to use validation in WPF can be found here:
If validation fails, it is desirable to show an error message with a hint in the GUI. In the MSDN library Microsoft shows an example of how to implement this (http://msdn.microsoft.com/en-us/library/system.windows.data.binding.validationrules%28v=vs.110%29.aspx).:
<Style x:Key="textBoxInError" TargetType="{x:Type TextBox}">
 <Style.Triggers>
  <Trigger Property="Validation.HasError" Value="true">
   <Setter Property="ToolTip"
           Value="{Binding RelativeSource={x:Static RelativeSource.Self},
                           Path=(Validation.Errors)[0].ErrorContent}" />
  </Trigger>
 </Style.Triggers>
</Style>
This works fine, but produces some exceptions in the Output window, if the error is corrected after occurring:
System.Windows.Data Error: 17 :
Cannot get 'Item[]' value (type 'ValidationError') from '(Validation.Errors)'
(type 'ReadOnlyObservableCollection`1').
BindingExpression:Path=(0)[0].ErrorContent; DataItem='TextBox'
(Name='Input1'); target element is 'TextBox'
(Name='Input1'); target property is 'ToolTip' (type 'Object')
ArgumentOutOfRangeException:'System.ArgumentOutOfRangeException: Specified
argument was out of the range of valid values.
Parameter name: index'
The Attached Property Validation.Errors of the Validation class has no elements, accessing the first element results in an ArgumentOutOfRangeException. System.Collections.ObjectModel.ReadOnlyObservableCollection<ValidationError> is the type of the Validation.Errors Attached Property. This type is in XAML wrapped by CollectionView. Instead of using the index of the ReadOnlyObservableCollection that lead to an exception, if there are no errors, we can use the CurremtItem property of the CollectionView. The property will return nothing if there are no errors, so no exception will be produced:
<Setter Property="ToolTip"
        Value="{Binding RelativeSource={x:Static RelativeSource.Self},
                 Path=(Validation.Errors).CurrentItem.ErrorContent}"/>

Saturday, May 24, 2014

Enumeration in WPF/XAML


Enumeration types  can be set by the keyword enum.Often it is needed to view all values of the enumeration. The following example shows how this can be done by setting all values to a ComboBox, so that it can be selected for further actions.

First I have defined an enum.

public enum State
{
    Unknown,
    Idle,
    WaitingForInput, 
    NoDisplayState
}

To show all Enum values in a ComboBox I have set the namespace of mscorlib to get Enum and the namespace of the local assembly to get then enum State.

xmlns:sys="clr-namespace:System;assembly=mscorlib"
xmlns:local="clr-namespace:EnumExample"

Then I have defined an ObjectDataProvider to get all State values.

    <Window.Resources>
        <ObjectDataProvider x:Key="StateValues"
                        ObjectType="{x:Type sys:Enum}"
                        MethodName="GetValues">
            <ObjectDataProvider.MethodParameters>
                <x:Type TypeName="local:State" />
            </ObjectDataProvider.MethodParameters>
        </ObjectDataProvider>
    </Window.Resources>

The ObjectDataProvider is referenced by the defined x:Key in the ItemsSource of the ComboBox.

        <ComboBox ItemsSource="{Binding Source={StaticResource StateValues}}"
                  SelectedItem="{Binding SelectedStateValue}"/>

Instead of setting an ObjectDataProvider in the XAML View, it is possivle to set an Array in code-behind or in the ViewModel.

StateEnums = Enum.GetNames(typeof(State));

public Array StateEnums
{
    get;
    set;
}

The Array is then binded to the ItemsSource of the ComboBox.

        <ComboBox ItemsSource="{Binding StateEnums}"
                  SelectedItem="{Binding SelectedStateEnum}" />


Wednesday, April 30, 2014

Using SQLite in an Android app

Within the Android APIs the package android.database.sqlite contains classes to simplify the usage of SQLite database. The easiest way to work with SQLite is to extend the SQLiteOpenHelper.
public class ApplicationDbHelper extends SQLiteOpenHelper {
But at first we are starting with the database design. After that we are creating a contract class that contains for each table a inner class. Each inner class implements the interface BaseColumns from the package android.provider and contains the table name and each column name. The primary key field _ID is inherited from the interface BaseColumns. Furthermore we also add a inner class for a view that we want to use.
public class ApplicationDbContract { 
    public ApplicationDbContract() {}

    public static abstract class Entry implements BaseColumns {
        public static final String TABLE_NAME = "entry";
        public static final String COLUMN_NAME_DESCRIPTION = "description";
        public static final String COLUMN_NAME_VALUE = "value";
        public static final String COLUMN_NAME_DATE = "date";
        public static final String COLUMN_NAME_CATEGORY_ID = "categoryId";
    }

    public static abstract class Category implements BaseColumns {
        public static final String TABLE_NAME = "category";
        public static final String COLUMN_NAME_CATEGORY_NAME = "categoryName";
    }

    public static abstract class ApplicationEntryCategory {
        public static final String VIEW_NAME = "entryCategory";
        public static final String COLUMN_NAME_ID = "id";
        public static final String COLUMN_NAME_DESCRIPTION = "description";
        public static final String COLUMN_NAME_VALUE = "value";
        public static final String COLUMN_NAME_CATEGORY = "category";
        public static final String COLUMN_NAME_DATE = "date";
    }
}
While extending SQLiteOpenHelper we need to implement the abstract methods onCreate and onUpgrade. We also add a constructor which is invoking the constructor of the parent with 'Application.db' as database name and '1' as database version. The SQLiteOpenHelper offers an easy way to access the SQLiteDatabase by calling getWriteableDatabase or getReadableDatabase. We add a private static SQLiteDatabase object to ApplicationDbHelper that is instantiated in the constructor only once in the application. This is done to prevent DatabaseObjectNotClosedException.
    private static SQLiteDatabase _db;
    public ApplicationDbHelper(Context context) {
        super(context, "Application.db", null, 1);

        if(_db == null || !_db.isOpen()) {
            _db = getWritableDatabase();
        }
    }
To close the database a static method is added that is called at application end.
    public static void shutDown() {
        if(_db != null && _db.isOpen()) {
            _db.close();
        }
    }
At next we are adding the SQLite statements to ApplicationDbHelper that will create our tables.
    private static final String SQL_CREATE_CATEGORIES =
           "CREATE TABLE IF NOT EXISTS " +
           ApplicationDbContract.Category.TABLE_NAME" (" + ApplicationDbContract.Category._ID +
           " INTEGER PRIMARY KEY," +                    
           ApplicationDbContract.Category.COLUMN_NAME_CATEGORY_NAME +
           " TEXT COLLATE NOCASE UNIQUE ON CONFLICT IGNORE)";
    private static final String SQL_CREATE_ENTRIES =
           "CREATE TABLE IF NOT EXISTS " +
           ApplicationDbContract.Entry.TABLE_NAME + " (" +
           ApplicationDbContract.Entry._ID + " INTEGER PRIMARY KEY," + 
           ApplicationDbContract.Entry.COLUMN_NAME_DESCRIPTION + " TEXT," +
           ApplicationDbContract.Entry.COLUMN_NAME_VALUE + " REAL," +
           ApplicationDbContract.Entry.COLUMN_NAME_DATE + " TEXT," +
           ApplicationDbContract.Entry.COLUMN_NAME_CATEGORY_ID + " INTEGER," +
           "FOREIGN KEY(" + ApplicationDbContract.Entry.COLUMN_NAME_CATEGORY_ID +
           ") REFERENCES " + ApplicationDbContract.Category.TABLE_NAME + "(" +
           ApplicationDbContract.Category._ID + ")" + ")";
     private static final String SQL_CREATE_VIEW_ENTRIES_CATEGORY =
            "CREATE VIEW " + ApplicationDbContract.EntryCategory.VIEW_NAME + " AS " +
            "SELECT " + 
            "String.format("E.%s AS %s, E.%s AS %s, E.%s AS %s, E.%s AS %s, C.%s AS %s",
            ApplicationDbContract.Entry._ID, 
                     ApplicationDbContract.EntryCategory.COLUMN_NAME_ID,
            ApplicationDbContract.Entry.COLUMN_NAME_DESCRIPTION,
                     ApplicationDbContract.EntryCategory.COLUMN_NAME_DESCRIPTION,
            ApplicationDbContract.Entry.COLUMN_NAME_VALUE,
                     ApplicationDbContract.EntryCategory.COLUMN_NAME_VALUE,
            ApplicationDbContract.Entry.COLUMN_NAME_DATE,
                     ApplicationDbContract.EntryCategory.COLUMN_NAME_DATE,
            ApplicationDbContract.Category.COLUMN_NAME_CATEGORY_NAME,
                     ApplicationDbContract.EntryCategory.COLUMN_NAME_CATEGORY) +
            " FROM " + String.format("%s AS E, %s AS C",
                                     ApplicationDbContract.Entry.TABLE_NAME,
                                     ApplicationDbContract.Category.TABLE_NAME) +
            " WHERE " + String.format("E.%s = C.%s",
                                      ApplicationDbContract.Entry.COLUMN_NAME_CATEGORY_ID,
                                      ApplicationDbContract.Category._ID) +
            " ORDER BY " + ApplicationDbContract.EntryCategory.COLUMN_NAME_DATE + " DESC";
These statements are executed in the overriden method onCreate.
    @Override
    public void onCreate(SQLiteDatabase sqLiteDatabase) {
        sqLiteDatabase.execSQL(SQL_CREATE_CATEGORIES);
        sqLiteDatabase.execSQL(SQL_CREATE_ENTRIES);
        sqLiteDatabase.execSQL(SQL_CREATE_VIEW_ENTRIES_CATEGORY);
    }
To add new categories and new entries to the database a ContentValues object and the insert method of SQLiteDatabase are used.
    public long addCategory(String category) {
        android.util.Log.v("DbHelper", "Add category");
        ContentValues values = new ContentValues();
        values.put(ApplicationDbContract.Category.COLUMN_NAME_CATEGORY_NAME, category);
        
        long newRowId = -1;
        if (_db != null) {
            newRowId = _db.insert(
                    ApplicationDbContract.Category.TABLE_NAME,
                    null,
                    values);
        }

        return newRowId;
    }
    public long addEntry(String description, Date date, double value, String category) {
        android.util.Log.v("DbHelper", "Add entry");
        
        // get the category id of the category name
        // category id is '-1' if category name is not existing
        long categoryId = getCategoryId(category);

        // if category not exists, add it
        if (categoryId == -1) {
            categoryId = addCategory(category);
        }

        SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd");
        String stringDate = format.format(date);
        ContentValues values = new ContentValues();
        values.put(ApplicationDbContract.Entry.COLUMN_NAME_DESCRIPTION, description);
        values.put(ApplicationDbContract.Entry.COLUMN_NAME_DATE, stringDate);
        values.put(ApplicationDbContract.Entry.COLUMN_NAME_VALUE, value);
        values.put(ApplicationDbContract.Entry.COLUMN_NAME_CATEGORY_ID, categoryId);

        long newRowId = -1;
        if (_db != null) {
            newRowId = _db.insert(
                    ApplicationDbContract.Entry.TABLE_NAME,
                    null,
                    values);
        }

        return newRowId;
    }
To read from the database the query method can be used, which retrurns a Cursor object. The Cursor object contains all queried rows. With the method moveToFirst the read position is placed on the first entry of the results. On each position the getColumnIndexOrThrow method can be used to get the corresponding column index of a column name. Furthermore the Cursor object offers methods to get the value of a column in a certain type.
    public long getCategoryId(String category) {
        android.util.Log.v("DbHelper", "Get category");

        // the id is set to '-1', it is only changed, if the category exists
        long id = -1;
        String[] columns = {ApplicationDbContract.Category._ID};
        String selection = ApplicationDbContract.Category.COLUMN_NAME_CATEGORY_NAME +
                           " = ?";
        String[] selectionArgs = {category};

        if (_db != null) {
            Cursor c = _db.query(
                    ApplicationDbContract.Category.TABLE_NAME, // table name
                    columns,                                   // results
                    selection,                                 // where columns
                    selectionArgs,                             // where conditions
                    null,                                      // group by
                    null,                                      // having
                    null);                                     // order by

            // if category exists there is one row, otherwise there is no result
            if (c.moveToFirst()) {
                // id is changed, if Cursor c is not empty
                id = c.getLong(c.getColumnIndexOrThrow(
                                      ApplicationDbContract.Category._ID));
            }

            // close Cursor to prevent DatabaseObjectNotClosedException
            c.close();
        }

        return id;
    }
To cycle through all results in the Cursor the method moveToNext can be used.
    public String[] getCategories() {
        android.util.Log.v("DbHelper", "Get categories");

        List<String> values = new ArrayList<String>();
        String[] columns = {
                ApplicationDbContract.Category._ID,
                ApplicationDbContract.Category.COLUMN_NAME_CATEGORY_NAME
        };
        String sortOrder = ApplicationDbContract.Category.COLUMN_NAME_CATEGORY_NAME +
                           " ASC";
 
        if (_db != null) {
            Cursor c = _db.query(
                    ApplicationDbContract.Category.TABLE_NAME, // table name
                    columns,                                   // results
                    null,                                      // where columns
                    null,                                      // where conditions
                    null,                                      // group by
                    null,                                      // having
                    sortOrder                                  // order by
            );

            while (c.moveToNext()) {
                String value = c.getString(c.getColumnIndexOrThrow(ApplicationDbContract.
                                         Category.COLUMN_NAME_CATEGORY_NAME));
                values.add(value);
            }

            // close Cursor to prevent DatabaseObjectNotClosedException
            c.close();
        }

        return values.toArray(new String[values.size()]);
    }
The method rawQuery is an alternative to the query method. It returns also a Cursor object.
    public List<Entry> getEntries() {
        android.util.Log.v("DbHelper", "Get entries");

        List<Entry> entries = new ArrayList<Entry>();
        String query = "SELECT * FROM " + ApplicationDbContract.EntryCategory.VIEW_NAME;

        if (_db != null){
            Cursor c = _db.rawQuery(query, null);
            while(c.moveToNext()){
                Entry entry = new Entry();
                entry.id = c.getLong(c.getColumnIndexOrThrow(
                         ApplicationDbContract.EntryCategory.COLUMN_NAME_ID));
                entry.description = c.getString(c.getColumnIndexOrThrow(ApplicationDbContract.
                                         EntryCategory.COLUMN_NAME_DESCRIPTION));
                entry.value = c.getDouble(c.getColumnIndexOrThrow(ApplicationDbContract.
                                         EntryCategory.COLUMN_NAME_VALUE));
                String date = c.getString(c.getColumnIndexOrThrow(ApplicationDbContract.
                                         EntryCategory.COLUMN_NAME_DATE));
                SimpleDateFormat dbFormat = new SimpleDateFormat("yyyy-MM-dd");

                try {
                    entry.date = dbFormat.parse(date);
                } catch (ParseException e) {
                    e.printStackTrace();
                }

                entry.category = c.getString(c.getColumnIndexOrThrow(ApplicationDbContract.
                                         EntryCategory.COLUMN_NAME_CATEGORY));
                entries.add(entry);
            }

            // close Cursor to prevent DatabaseObjectNotClosedException
            c.close();
        }

        return entries;
    }
To delete a row from a table, the method delete can be used.
    public void deleteEntry(long id) {<
        android.util.Log.v("DbHelper", "Delete entry");

        String selection = ApplicationDbContract.Entry._ID + " = ?";
        String[] selectionArgs = {String.valueOf(id)};

        if (_db != null) {
            _db.delete(ApplicationDbContract.Entry.TABLE_NAME, selection, selectionArgs);
        }
    }
To update a row in a table the ContentValues object and the update method can be used.
    public void updateEntry(){
        android.util.Log.v("DbHelper", "Update entry");

        ContentValues values = new ContentValues();
        values.put(ApplicationDbContract.Entry.COLUMN_NAME_DESCRIPTION, "");

        String selection = ApplicationDbContract.Entry._ID + " = ?";
        String[] selectionArgs = { String.valueOf(0) };

        if (_db != null) {
            _db.update(
                    ApplicationDbContract.Entry.TABLE_NAME,
                    values,
                    selection,
                    selectionArgs
            );
        }
    }