Tuesday, January 15, 2013

Simple C# .ofx (Open Financial Exchange) library

I needed a quick and simple way to read the .ofx files that can be downloaded from ABSA bank. I know there are some libraries available online, but I was not in the mood to go figure out how they work, compile and then eventually come to the conclusion that it will not work for me.
So I quickly wrote a very simple C# library. It works and that is good enough for me. It does not implement the entire OFX specification and it does not allow writing these files. Read only. It’s available from my GitHub repository. If you want to make changes or suggestions, feel free to do so and commit them back.
Library can be found here: GitHub Repository - christogreeff/ofx
I will try to add simple libraries to my GitHub repository from time to time. It is high time I add some code there.

Wednesday, September 12, 2012

Google Nexus 7 initial impressions

My initial impressions in a couple of sentences. So far it is a beautiful little device to use.

What I like: The responsiveness, the display, the size and Android Jelly Bean 4.1.1.

What I think could be improved, but I'm happy with for now: Audio quality, battery life and the quality of front facing camera.

What I don't like: The US spec charger and that it has no SD card slot.

For the price of the Nexus 7, there's not really much to fault.

Wednesday, March 28, 2012

Dear ABSA

A long, long time ago, my parents opened a Trustbank account in my name. I had my own bank account!

I was taught how to save money and to spend it wisely. If I had money in the bank, the bank would give me more money. As a little boy, I thought this was how the bank thanked me for saving my money with them. Today I know it is called interest. Today I know better.

Trustbank disappeared. ABSA was born.

Throughout high school, I saved pocket money in my bank account. My bank balance never did contain too many digits, but it was my bank account and I was proud of it.

I went on to study and I used your student account offering. In those days, money did disappear at an alarming rate - the life of a student. I always kept my account in the black. I remember an incident where you guys for some reason deducted a lot of additional money from my account each month. At first, I did not know why. Eventually I discovered you decided that I should be charged monthly managed fee. I was not happy, but it was fixed. Banks also make mistakes, right?

My career started and more funds were deposited into my account. You upgraded my account and my service fees increased, but working adults pay service fees I thought. During those first years, I bought my first brand new car. Your vehicle finance made it possible, although the rates were a little on the high side. I think I started to feel used.

Barclays bought a stake in your company. Your stationary changed slightly. You still sponsored Noot vir Noot.

I am some years older now. For the last couple of years, I have felt that my loyalty means nothing to you. I still make use of your vehicle finance. I make use of your investment offerings. I have a cheque account and a credit card.

Your service fees are really high. Another bank gave me a home loan, because you could not compete with their rates. You have wanted me to invest some of my money with more of your investment offerings, but your consultant never phoned me back.

Three months ago I stopped using your credit card. I received a credit card from another bank and their service seems to be better. I have been putting off this decision for months because it will mean the end of something that started many years ago. Maybe it is time to move on. You have changed and I do not think you care much about loyalty. The bank I grew up with does not exist anymore.

“Today, tomorrow, goodbye”

Kind regards,
Christo

P.S. You’re trending on something called Twitter.

Friday, February 3, 2012

Dear Incredible Connection


I was just in one of your stores. The one in Bayside, Table View. The one close to the sea. I don’t know how this is relevant, but it popped into my head.

Anyway, I digress. I was in your store to make a purchase. You know, pur·chase [pur-chuhs] [verb (used with object) "to acquire by the payment of money or its equivalent; buy."]

Something computer related that I own broke and I need to replace it immediately. I stood around in your store, comparing the different options. There were some nice objects to acquire by the payment of money. Nobody seemed interested to help me. There were some nice young men, fooling around and laughing. They wore blue shirts. Is it blue? I am not sure. Blue-ish. They ignored me. They probably had important business to attend to.

I also own a smartphone. I Googled the options and an online store has it for cheaper! However, I need it now. Dilemma. I do not want to disturb the young men. Therefore, I left. My purchase will have to wait. Online places do free delivery nowadays. Seems like less hassle.

I also need a new notebook. I am afraid the blue-ish men in your store might be busy indefinitely, so I will not go back there.

I thought I would share this experience with you. Reading it now, I come across as being a little self-centred. On second thought, ignore this completely. You might be busy.

Kind regards,
Christo

Wednesday, September 28, 2011

Amazon Kindle Fire

The much speculated Amazon Kindle tablet was released earlier today. I must say, I should probably have waited a bit longer before ordering my Kindle. But then again, looking at the Kindle Fire my Kindle Keyboard is still what I would have bought.

I’ll wait for the reviews, but if you want the latest new toy from Amazon, buy the Kindle Fire, Full Color 7" Multi-touch Display, Wi-Fi.

The most interesting aspect of the Kindle Fire seems to be the Silk browser. Watch the short video below. It does make sense from a tablet perspective to leverage the Amazon EC2 service to speed up browsing on the device. Time will tell if it really makes such a big difference.

Silk Browser

Sunday, March 7, 2010

Recruiters and talent

From time to time I get emails from recruiters who either have my details on record or from my publicly available profile on LinkedIn. Now I don’t have a problem with them sending me the emails. It is always the content or the lack thereof that determines whether or not I click the delete button – a number of occasions the real position might have been wonderful, but the email (which is on many occasions just a copy and paste from the actual posting) did not address the true nature of the job. Many of these job emails always seem to lack the basic information that any potential job seeker need to make a decision.

There are many recruitment companies that are very good at what they do, but I wish *all* of them would read this post by Eric Lippert of Fabulous Adventures In Coding.

Information inadequacy of job postings seems to be a problem for many software developers looking for the next interesting position. I think recruiters should be made aware of this fact. And I think I will from now on be more vocal about what I expect to see in job emails/postings.

Monday, July 27, 2009

Taking the road less travelled

Sometimes making a rash decision, turns out to be quite fulfilling. I took a couple of photographs and amongst them were these 2 pictures. Taken on my way home, just outside Cape Town. I took a left turn at a certain intersection, instead of driving across, like I normally do.

Sometimes we should apply this to software development also. Try a different approach to a known solution.  It might end up being more interesting.

09

10

Thursday, July 23, 2009

Pre-order Windows 7

No, you can’t. Not in South Africa anyway. I spent a good couple of minutes (maybe more than 20) this morning trying how to pre-order, order, buy, rent and/or lease Windows 7.

So I’ve concluded that Microsoft does not want to sell Windows 7 to us. The Microsoft South Africa website has a buy/offers/upgrade page, but this does not even work properly.

mssa01

Click the link and… no. I did not mean “South Africa 404 mspx”.

mssa02

I recently (8 months ago) purchased a new notebook pre-installed with Windows Vista. So for a couple of months I’ve been running Windows 7 RC instead. Do I want to run Windows 7 in the future? Yes. Do I want to buy it? Yes.

So I pose this question to Microsoft: Will you please sell Windows 7 to me? Now? On pre-order?

Monday, October 27, 2008

Paging records using the ROW_NUMBER() function in SQL Server 2005

Early this morning I was confronted with a question. In general I like questions. I don't always know the answer (contrary to popular belief), so it was one of those Monday morning questions that make you sit up and think for a couple of minutes.

How easy is it to implement paging in SQL Server?

I don't know. I have never had to use paging in SQL. I do know MySQL provides the LIMIT keyword that allow you to specify an offset with the amount of records you need. LIMIT 100,20 will return results 100 to 120.

What about SQL Server 2005. Well, there's an temporary table solution that I've heard of but haven't seen implemented, partly because I try to avoid temporary tables.

Well, we're in luck. SQL Server 2005 introduced a new function called ROW_NUMBER(). So how does it work?

select
result.Firstname,
result.Lastname,
result.Date
from
(
select
Firstname, Lastname, Date,
row_number()
over (order by Date asc) as row
from
RegisteredUsers)
as result
where
result.row
>= 20 and
result.row
<= 30

From the MSDN entry: "Returns the sequential number of a row within a partition of a result set, starting at 1 for the first row in each partition."


So it returns a sequential row counter within a partition of a result set. The only major remark is that you have to specify the ORDER BY clause, because it determines the order in which rows are assigned their row numbers.


The stored procedure below takes everything into account. Calling it requires you to specify a page number and a page size. The result set is also limited by the TOP expression to limit the number of results. I think it might help improve performance on large result sets.


create procedure dbo.GetCustomersPage
@PageNumber int,
@PageSize int
as
begin

declare @Offset int
declare @Limit int

set @Offset = 1 + (@PageNumber - 1) * @PageSize
set @Limit = @Offset + @PageSize

select
result.Firstname,
result.Lastname,
result.Date
from
(
select
top (@Limit)
Firstname, Lastname, Date,
row_number()
over (order by Date asc) as row
from
RegisteredUsers)
as result
where
result.row
between @Offset and @Limit

end

I think this solution is really simple and straightforward.  Comments are always welcome and appreciated.


Environment:
Microsoft ® Visual Studio 2008
Microsoft ® .NET framework 3.5
Microsoft ® Windows XP (SP3)

Monday, October 13, 2008

C# Asynchronous methods with CallBack

It has been quite a while since my last post, so I decided to create a follow-up to a post I made last year. At the time I showed how to overcome the “cross-thread operation not valid” problem when updating the user interface. For this post I decided to tackle something more or less along the same lines, but instead focusing more on how to perform an asynchronous operation with a callback.

When creating a single-threaded application we normally have to wait for a long-running operation, such as a call to the database, to complete before we can update the user interface again. This creates an application that appears to be slow and unresponsive. The reason is that the long-running operations block other operations from continuing.

So what do we do? Using C# it is fairly straightforward to delegate long-running operations to background threads, so that our foreground thread updating the user interface stays responsive and allow other operations to be completed.

For this post I chose to show the how to execute a callback when an asynchronous method completes. There are other methods to use such as using the EndInvoke() Call Pattern, using WaitHandles to Wait for the Call to complete or the Polling Call Pattern. I find a Callback makes the most sense to me.

From my experience I found the following events occur the most in the type of applications I write:

  1. A control is clicked, indicating that some long-running operation will be fired
  2. Controls on the user interface needs to be set
  3. Long-running operation is started
  4. Long-running operation retrieves/generates data/updates user interface and/or performs magic
  5. Long-running operation ends
  6. Controls on the user interface needs to be set/populated

If we look at my code example, the above-mentioned steps are mapped to the following methods:

  1. private void buttonGo_Click(object sender, EventArgs e)
  2. private void SetFormControls(bool Enabled) in Step 1
  3. private bool SomeLongOperation() in Step 1
  4. private bool SomeLongOperation()
  5. private void SomeLongOperationCallBack(IAsyncResult result)
  6. private void SetFormControls(bool Enabled) in Step 5

Now for the code. I've (over-)commented the code to help explain. The example starts an asynchronous operation, updates a control on the form and executes a callback when done. It's a simple example, but I am of the opinion that it covers all of the basics.

using System;
using System.Text;
using System.Threading;
using System.Windows.Forms;

namespace WindowsFormsApplicationDemo
{
public partial class frmDemo : Form
{
public frmDemo()
{
InitializeComponent();
}

// Creates a delegate with the same signature as the method used for a long operation
// In this case we'll use SomeLongOperation()
private delegate bool SomeLongOperationDelegate();

// Creates a delegate with the same signature as the method used to update the UI
// In this case we'll use UpdateUIHelper(string value)
private delegate void UpdateUIDelegate(string value);

// Creates a delegate with the same signature as the method used to set form controls
// In this case we'll use SetFormControls(bool Enabled)
private delegate void SetFormControlsDelegate(bool Enabled);

/// <summary>
/// User actions start the long-running operation
/// Step 1, 2 and 3
/// </summary>
private void buttonGo_Click(object sender, EventArgs e)
{
SetFormControls(
false);

this.UpdateUI("Starting...");

// create an instance of SomeLongOperationDelegate
SomeLongOperationDelegate someLongOperationDelegate = new SomeLongOperationDelegate(this.SomeLongOperation);

// start asynchronous operation, pass someLongOperationDelegate as a parameter, so we can get it back later on callback
someLongOperationDelegate.BeginInvoke(new AsyncCallback(this.SomeLongOperationCallBack), someLongOperationDelegate);
}

/// <summary>
/// Sets any controls on the form that you want to have set prior to starting the long operation
/// </summary>
/// <param name="Enabled">Enable or disable controls</param>
private void SetFormControls(bool Enabled)
{
// update any controls here, if you want to (such as enable or disable controls)
// this.buttonGo.Enabled = Enabled;
}

/// <summary>
/// Long operation such as a call to the database
/// Step 4
/// </summary>
/// <returns>bool value to indicate success</returns>
private bool SomeLongOperation()
{
// perform some longish operation

for (int i = 1; i < 8; i++)
{
this.UpdateUI(
new StringBuilder("Operation busy ").
Append(i.ToString()).
Append(
"... (Managed TID: ").
Append(Thread.CurrentThread.ManagedThreadId.ToString()).
Append(
")").ToString());

Thread.Sleep(
150);
}

return true;
}

/// <summary>
/// CallBack for the someLongOperationDelegate instance
/// Step 5 and 6
/// </summary>
/// <param name="result">IAsyncResult object</param>
private void SomeLongOperationCallBack(IAsyncResult result)
{
// original delegate passed in the asyncState parameter, get it back here
((SomeLongOperationDelegate)result.AsyncState).EndInvoke(result);

// update the user interface
this.UpdateUI(
new StringBuilder("CallBack (Managed TID: ").
Append(Thread.CurrentThread.ManagedThreadId.ToString()).
Append(
")").ToString());

// start an asynchronous operation to set the controls on the form
this.BeginInvoke(new SetFormControlsDelegate(this.SetFormControls), new object[] { true });
}

/// <summary>
/// Checks whether or not an Invoke is required and calls the method accordingly
/// </summary>
/// <param name="value">value of string to go into the listbox</param>
private void UpdateUI(string value)
{
// if the calling thread is not the same thread that created the controls to be updated
// call an Invoke to update controls
if (this.InvokeRequired)
this.Invoke(new UpdateUIDelegate(this.UpdateUIHelper), new object[] { value });
else
this.UpdateUIHelper(value);
}

/// <summary>
/// Updates the user interface with whatever needs to be updated
/// </summary>
/// <param name="value">value of string to go into the listbox</param>
private void UpdateUIHelper(string value)
{
listboxResults.Items.Add(
new StringBuilder(value).
Append(
" (Parent Managed TID: ").
Append(Thread.CurrentThread.ManagedThreadId.ToString()).
Append(
")").ToString());

listboxResults.SelectedIndex
= (listboxResults.Items.Count - 1);
}
}
}

 




Some afterthoughts:



  1. Make sure that you understand delegates and their use. They are very helpful and are needed when you want to make more use of events you application.

  2. Disable the controls that initiate the long-running operation if they are only needed for that purpose. Other controls such are grids or items that are updated to indicate progress, should be visible. Grids or controls populated with large amounts of data, may want to made invisible while the long-running operation is populating them.

  3. Make sure that you understand how Winforms managed threads are used. It will help you understand why you need to Invoke to update controls.

  4. When using BeginInvoke on a delegate, make sure you call EndInvoke to free any resources.  When doing a this.BeginInvoke(...) and there are no results to harvest, you do not need to call EndInvoke(). Chris Sells has a paragraph or two in his book, Windows Forms Programming in C#. There are numerous discussions on BeginInvoke/EndInvoke available on the Internet, but from my experience, when following this simple rule, you should be ok.

Please feel free to comment on this post.  It is always interesting to hear the opinions from other people.


Environment:
Microsoft ® Visual Studio 2008
Microsoft ® .NET framework 3.5
Microsoft ® Windows XP (SP3)