Thursday, February 21, 2008

C# .NET Programming Tip: Types

Figuring out a variable's type has become more important since now variables can be boxed by their parent class(s) (Where all can be "Object"). It's nice because it allows for one generalized function to work with many types that perform an action on a common attribute, or first figure out what the object is and then perform the action.

That is one of the instances where figuring out a variables type is important.

.NET has a built-in function called GetType() which figures out what type a variable is.


//loop through the controls in the panel anf figure out which of the checkboxes are checked
foreach (Control panelControl in newCheckListQuestionPanel.Controls)
{
//only continue if this control is a checkbox
if (panelControl.GetType().ToString().Equals("System.Windows.Forms.CheckBox") == true)
{
if (((CheckBox)panelControl).Checked == true)
{
//do something here if the control is checked
}
}
}

Sunday, February 17, 2008

C# .NET Programming Tip: Connecting to an Oracle Database

Please view this post for a better way to connect to Oracle.

Ugh, I spent a good 6 hours figuring out how to do this! Hopefully this post can save someone some time.

Connecting to an Oracle database requires driver files from Oracle. This is true no matter what you use in Visual Studio, it true for System.Data.ODBC or whatever else. This little factoid took me a while to figure out, as I didn't want to believe that I had to install a 200mb piece of software from Oracle just to connect to one of their databases.

Well thankfully I didn't have to install their standard client. They offer an "instant client" that is quite a bit smaller. They even offer a lite version of the instant client, which is the one I decided to use. The bad thing about it is even that is around 20mb in size.

You can download it here:
http://www.oracle.com/technology/software/tech/oci/instantclient/index.html

Even downloading that I had issues. I tried downloading the 11.1.0.6.0 version of the Instant Client Lite, but I kept getting errors on their website, so I just settled for 10.2.0.3.


*Instant Client Package - Basic Lite: Smaller version of the Basic, with only English error messages and Unicode, ASCII, and Western European character set support (10.2 only)
instantclient-basiclite-win32-10.2.0.3-20061115.zip (10,360,970 bytes)

You only need the one zip file. The other ones like SDK and ODBC are not necessary unless you want to specifically use ODBC. This file above allows you to connect with using System.Data.OracleClient. In Visual Studio go to the menu item "Project->Add Reference..." and find System.Data.OracleClient in there and check it. All of the computers I have tried have the reference available (.NET 2.0 version).

If for whatever reason it is not in there, Microsoft has their old .NET 1.1 version for download on their site HERE. Good luck finding the 2.0 version though.

The instant client zip file you downloaded has a few files in it.

We need these four:
oci.dll
orannzsbb10.dll
oraocci10.dll
oraociicus10.dll

Place those into the directory of your project EXE. Most likely that will be in the "Project_folder/bin/debug". Of course once you publish your EXE, you will have to place the DLLs in their proper place.

There is one other file you must have. It's called "tnsnames.ora" and is another thing that wasted a lot of my time figuring out. It basically tells the dlls where and how to connect to the database.

Here is an example of a tnsnames.ora file:
AAAA.WORLD =
(DESCRIPTION =
(ADDRESS = (PROTOCOL = TCP)(HOST = XXXXXXXXXX)(PORT = 1521))
(CONNECT_DATA = (SID = AAAA)))
AAAA.WORLD is the Oracle database you are trying to connect to. The SID parameter is for older versions of Oracle. Just put the first part of your database name in there. The database at my job is using version 8 of Oracle, so the newer parameter called "SERVICE_NAME" would not work. The HOST parameter is where you put your IP address, or server name (say you have an oracle server named DBNINJA, just put that in there). The PORT 1521 is the default for Oracle, so if you have troubles, make sure your database administrator didn't change the port.

What I'm hoping to figure out eventually is how to input this information in the connection object in Visual Studio, without having this file (ed: done, see newer post). This thing doesn't make sense, because programs should be able to connect to more then one database instance. Anyways, I'll have to do more research on that and post my finding here in the future. This whole tnsnames.ora file seems like a bad idea.

Now here is how to connect to the database in Visual Studio 2005:

using System.Data.OracleClient;

...

StringBuilder tableList = new StringBuilder();

try
{
using (OracleConnection testConnection = new OracleConnection("Data Source=AAAA.WORLD;Persist Security Info=true;User Id=******;Password=******;"))
{
testConnection.Open();

using (OracleCommand testCommand = testConnection.CreateCommand())
{
testCommand.CommandText = "SELECT TABLE_NAME FROM ALL_CATALOG";
testCommand.Prepare();

using (OracleDataReader testReader = testCommand.ExecuteReader())
{
while (testReader.Read())
{
tableList.Append("Table: " + testReader.GetString(0) + "\r\n");
}
}
}

testConnection.Close();
}
}
catch (Exception error)
{
System.Windows.Forms.MessageBox.Show(error.ToString());
}

txtDisplay.Text = tableList.ToString();


Notice that the data source points to the same place as the tnsnames.ora file. Also, the connection string here is where you place your username and password. After creating the connection object, you open the connection and perform some sql with a command object.

Also take note that I am using ... "using" statements. These automatically close and unset the objects after they have been used. It saves a lot of hassle!

The SQL statement "SELECT TABLE_NAME FROM ALL_CATALOG" fetches a list of every table that is available to the user and returns the name of each one.

The StringBuilder tableList I am using is just a faster way of concatenating strings.

That's it for now. I'm sure I will be doing a lot more with Oracle in the future, so I will probably have a lot more to write about here as well.

Saturday, February 16, 2008

C# .NET Programming Tip: Keeping A History With Properties

The program I am writing at work's primary functionality is a checklist. One of the requested features is the ability to record what actions a user performs on the checklist. So there needs to be some additional code that updates a history table whenever a user makes any changes to the checklist.

Let's define when a history event should fire off:
When the user makes a new checklist.
When the user updates any property of the checklist.
When a user archives a checklist (eg: discard in so many works).

Making a history entry when the user makes a new checklist or archives a checklist is easy. There would generally be only one function for each, so just before or after adding or archiving the checklist from the database, the history process is called.

What about when the user updates a single field in a checklist? It is important to track those changes, because a single little change is generally the most important (he said, she said situation).

In my program checklistprocessor, checklist, checklistitem, history, and database are all separate objects. So what would be an easy way to update each property individually and also record the before/after state in a history entry?

Use properties!

Here is an example of one such property:


class CheckListItem
{
private int id;
private int checkListId; //foreign key to CheckList
private string notes;
...

//will hold a reference that is received from a calling object
private Database dbConnection;

public string Notes
{
get { return this.notes; }
set
{
//create history entry for this change
History.add(id, checkListId, this.notes, value);

//update instance variable
this.notes = value;

//update database with new value
dbConnection.queryNoResult("UPDATE ... WHERE... ;")
}
}

...

Remember that C# is case sensitive. That means we can have a Notes property and a notes class variable. It makes things a lot simpler naming wise and is, from what I have read, an unofficial standard practice.

As you can see from the code, all the checklist has to do to update a checklistitem variable called "notes" is call the property Notes. Everything else is handled by the set method.

C# .NET Programming Tip: FlowLayoutPanel Mouse Scroll Wheel Not Working

Ahh, some if you may notice that the mouse scroll wheel doesn't work by default with FlowLayoutPanels. Yeah, this little thing has frustrated me for a while.

I usually check the Internet for solutions to problems I have been having. Most likely someone has posted on a message board about the same problem and the problem was solved. Sadly, this method of finding information can lead to numerous dead ends as people don't post the solution (eg: "Hey I figured it out!") and fail to say what they did. Or the solution they figure out isn't "good." Of course, programming blogs are also a good resource with generally higher quality information, but they don't seem to be as prevalent in search engines for some reason. Maybe blogs are generally lower traffic?

Like I said before, one such issue I was having was with FlowLayoutPanels and the mouse scroll wheel not scrolling them when the panels had scroll bars. Doing a search on the internet came up with close to nothing. Most "solutions" were to create your own functions to catch MouseEventArgs from the Form and perform the scroll on the FlowLayoutPanel yourself with scrolling code. Far from the best solution...

It didn't make sense. The panels have all of the events necessary to scroll themselves, but they were not working. Then I came into some luck and found someone talking about the same problem I was having. They suggested that the FlowLayoutPanels were not able to get focus, so that's why they would not scroll. Pure genius! They added one line of code to the Click event to whatever panel they wanted to scroll. Not really ideal as the user would have to know to click on the panel before they could scroll it. I took it one step further. Check out the code below.


private void newCheckListQuestionPanel_Click(object sender, EventArgs e)
{
newCheckListQuestionPanel.Focus(); //allows the mouse wheel to work after the panel is clicked
}
private void newCheckListQuestionPanel_MouseEnter(object sender, EventArgs e)
{
newCheckListQuestionPanel.Focus(); //allows the mouse wheel to work after the panel has had the mouse move over it
}

In Visual Studio, select the panel in the visual design window, and click the Events button in the properties window to find a list of events the control can do. I added events for Click and MouseEnter. Click probably isn't necessary, as MouseEnter is really the star of the show here. Everytime the user moves the mouse over the FlowLayoutPanel, the control receives focus, allowing the mouse wheel to work!

Yeah, it's that simple.

C# .NET Programming Tip: Using the Tag property of controls

I've been doing C# programming at my job lately. Throughout the process I've hit a few little walls that were difficult to find answers for and thought writing down the solutions here would be good for myself any anyone who happens to find this by-way-of search engine. So as I come upon these little bits of useful harder-to-find information, I'll write something here about it.

Creating windows form controls programmatically and passing data between class instances was one such issue. I did a number of searches on Google and didn't really find much help. The main suggestion was to create an EventArgs class that passes events or some such thing. That was a really involved process that didn't seem like it was worth the effort to implement.

I then created my own static global event class where anything in the program could pass an unlimited number of parameters. Once the parameters were accessed, they were deleted from the static Dictionary that stored everything. Well actually it was a Dictionary inside a Dictionary (parameterSetName -> (Dictionary that holds all parameters)). So it was a one shot wonder.

Let me give a little background information about why I needed to implement it:
I had a number of classes that generate their own windows controls to select or edit themselves. For example, A "checklist container" created controls to allow the user to select one specific checklist. The function was called with a reference to a FlowLayoutPanel. Then when the user clicked a button to select one specific checklist, the container class would call a function inside the selected checklist instance to tell it to display edit controls for itself. The problem was passing that FormLayoutPanel reference to the checklist function from an event function that I linked to the buttons that were dynamically generated.

There was really no easily noticeable way to do it. Every button the Container class created was all linked up into one Event function. The way .NET works is that you can create your own event function, but only two specific parameters are passed (one is the control itself, in the sender parameter).

So that is why I created that static global parameter class. The thing is I ran into another roadblock that made the global parameter class useless. They way I designed it is that the parameters were deleted from the Dictionary object once some function accessed them, but what if I wanted a form that did a similar process, but did not completely replace the current controls with something new (eg: the single checklist edit controls needed to perform actions like reading data from textboxes that were created at runtime. The function that would take events from the buttons on the edit form had no way of accessing textboxes at this point.

I go back on Google and Yahoo! and do a few more searches. This time I come upon the Tag property of Windows Form Controls. It's an object variable that you can put anything into and it works perfectly for what I was trying to do.

By this point you probably are having a hard time understanding what I am talking about. So here are a few code examples to help you out:

The way my program works is that checklist has multiple checklist items and each checklist item has a few windows form buttons that should do things.

This is a button that would fire off a request to update a database data from a textbox:


Button btnNotesChance = new Button();
btnNotesChance.Text = "Update Notes";
btnNotesChance.AutoSize = true;
pnlTxtBoxes.Controls.Add(btnNotesChance);
btnNotesChance.Name = "clintsbx-" + i.ToString(); //save a name so we can know who calls the event handler
btnNotesChance.Tag = txtNotes; //save the handle to the notes textbox into the button's "tag" variable
btnNotesChance.Click += new EventHandler(displayChecklistAsControls_Event);


Notice the Name property. I give it a unique identifier clintsbx-#, so that way I can know which "Update Notes" button was clicked in the function displayChecklistAsControls_Event. I'll get to the event function later. Also notice the Tag property. That stores the handle to the button's related TextBox "txtNotes" object (windows form textbox). So now when the button is clicked we will know who called the update and what textbox to take the information from!

Here is an excerpt from the event function:

private void displayChecklistAsControls_Event(Object sender, EventArgs e) //event handler for the checklist selector buttons
{
//fetch the calling control
Control senderControl = (Control)sender;

//take the calling control's name and split it up into two parts, one means the type of control and the other means
//the index of the control (eg: ollststatu-4 means the 4th listbox for checklistitem status)
string [] nameData = senderControl.Name.Split(Convert.ToChar("-"));

//turn the second array value into an integer
int itemSelected = Convert.ToInt32(nameData[1]);


As you can see there the sender variable is key. We can use that to get access to the button that called the event. Then we can take the Name of the control and the Tag of the control and do what we need to do.

I perform a Switch on the first nameData item to decide which set of controls called the event. Here is some code below for "update notes" I have been talking about.

case "clintsbx":
//take the notes textbox data and save it to the corresponding checklistitem notes field
//utilizes the button's tag element to save a reference to the corresponding textbox
string selectedItemText = ((TextBox)((Button)senderControl).Tag).Text;
//only update if the user made some type of change
if (checkListItems[itemSelected].Notes.Equals(selectedItemText) == false)
{
//update the instance and database with this text data
checkListItems[itemSelected].Notes = selectedItemText;
}
break;


It takes the button's Tag property and turns that into a reference to the textbox. Then it is able to access the Text property of the textbox and then update the database.

Problem solved!