Core C# and .NET
< Day Day Up > |
Several concepts were introduced in the first section. Let's bring them together in an application that relies on data binding to display and update information from the Films database. Figure 12-5 shows a screen shot of the application's user interface. Figure 12-5. Application combining complex and simple binding
Each control on the Windows Form except buttons is bound to a data source. A ListBox and ComboBox illustrate complex binding; two text boxes, a CheckBox, and a PictureBox, are bound using simple binding. The controls can be bound dynamically to either a data table or an array that contains custom objects. The Scroll button moves down the list box by internally using a binding manager to advance to the next item in the data source list. Let's dissect the program by looking at code associated with the buttons. Much of the code should be familiar from code segments in the previous section and does not require further explanation. Binding to a DataTable
The code in Listing 12-1 is executed when the Bind to Table button is clicked. It loads the necessary data from the Films database into a table and binds the controls on the form to it. This populates the ListBox and ComboBox with a list of movie titles. The value in the other controls is derived from the content of the current row (highlighted in the list box). The most interesting of these is the PictureBox, which has its BackgroundImage property bound to a column in the table containing images. Because the database does not contain images, the program adds this column to the data table and fills it with images for the movie in each row. Listing 12-1. Binding Controls to a DataSet
// Bind control to data from a database private void btnTableBind_Click(object sender, EventArgs e) { SqlConnection conn = new SqlConnection(GetString()); conn.Open(); ds = new DataSet("films"); string sql = "SELECT movie_ID, movie_title, movie_year, studio, afi_rank, CASE WHEN bestpicture ='Y' THEN 1 ELSE 0 END as BestPicture FROM movies ORDER BY movie_Year"; da = new SqlDataAdapter(sql, conn); // Command builder keeps track of changes to data SqlCommandBuilder sb = new SqlCommandBuilder(da); da.Fill(ds,"movies"); DataTable dt = ds.Tables[0]; Data Column dCol = new DataColumn("movie_Image", Type.GetType("System.Object")); dt.Columns.Add(dCol); // Place image in new column. Name is based on movie ranking. Image defaultImage = Image.FromFile(@"c:\defaultimg.jpg"); foreach (DataRow dRow in dt.Rows) { string rank = ((int)dRow["afi_rank"]).ToString(); string imgFile = "c:\\afi" + rank + ".gif"; try { Image imgObject = Image.FromFile(imgFile); dRow["movie_Image"] = imgObject; } catch (Exception ex) { dRow["movie_Image"] = defaultImage; } } // Nothing to this point should be considered a change dt.AcceptChanges(); // Bind listbox and combobox to datasource listBox1.DataSource = ds; listBox1.DisplayMember = "movies.movie_Title"; listBox1.ValueMember = "movies.movie_ID"; comboBox1.DataSource = ds; comboBox1.DisplayMember = "movies.movie_Title"; // Binding manager has global scope bmb = this.BindingContext[ds, "movies"]; bmb.PositionChanged += new EventHandler(bmb_PositionChanged); try { // TextBox.Text binds to studio name if(txtStudio.DataBindings["text"] != null) txtStudio.DataBindings.Remove( txtStudio.DataBindings["Text"]); txtStudio.DataBindings.Add("text", ds, "movies.studio"); // TextBox.Text binds to year movie released if(txtYear.DataBindings["text"] != null) txtYear.DataBindings.Remove( txtYear.DataBindings["Text"]); txtYear.DataBindings.Add("text", ds, "movies.movie_year"); // CheckBox.Checked - binds to best picture value (0 or 1) if (checkBox1.DataBindings["Checked"] != null) checkBox1.DataBindings.Remove( checkBox1.DataBindings["Checked"]); checkBox1.DataBindings.Add("Checked", ds, "movies.BestPicture"); // PictureBox.BackgroundImage Binds to image if (pictureBox1.DataBindings["BackgroundImage"] != null) pictureBox1.DataBindings.Remove( pictureBox1.DataBindings["BackgroundImage"]); pictureBox1.DataBindings.Add("BackgroundImage", ds, "movies.movie_Image"); } catch (Exception ex) { MessageBox.Show(ex.Message); } } Binding Controls to an ArrayList
Clicking the Bind to Array button, binds the controls to an ArrayList that is filled with instances of the custom class MyMovie (see Listing 12-2). After the data source is created, the binding process is identical to that followed with the data set. Listing 12-2. Binding Controls to an Array of Objects
// Bind control to array populated with instances of custom class private void BindToArray() { movieList = new ArrayList(); Image movieImg = Image.FromFile(@"c:\defaultimg.jpg"); // Create objects and add to array movieList.Add(new MyMovie("2","Casablanca",1942, "Warner Bros.",true, Image.FromFile("c:\afi2.gif"))); movieList.Add(new MyMovie("1","Citizen Kane", 1941, "RKO", false, Image.FromFile("c:\afi1.gif"))); movieList.Add(new MyMovie("4","Gone with the Wind", 1941, "Selznick International", true, Image.FromFile("c:\afi4.gif"))); // listBox1.DataSource = movieList; listBox1.DisplayMember = "Movie_Title"; // comboBox1.DataSource = movieList; comboBox1.DisplayMember = "Movie_Title"; bmb = this.BindingContext[movieList]; ; bmb.PositionChanged += new EventHandler(bmb_PositionChanged); if (txtStudio.DataBindings["Text"] != null) txtStudio.DataBindings.Remove( txtStudio.DataBindings["Text"]); txtStudio.DataBindings.Add("Text", movieList, "Studio"); // if (txtYear.DataBindings["Text"] != null) txtYear.DataBindings.Remove( txtYear.DataBindings["Text"]); txtYear.DataBindings.Add("Text", movieList, "Movie_Year"); // if (checkBox1.DataBindings["Checked"] != null) checkBox1.DataBindings.Remove( checkBox1.DataBindings["Checked"]); checkBox1.DataBindings.Add("Checked", movieList, "BestPicture"); // if (pictureBox1.DataBindings["BackgroundImage"] != null) pictureBox1.DataBindings.Remove( pictureBox1.DataBindings["BackgroundImage"]); pictureBox1.DataBindings.Add("BackgroundImage", movieList, "Movie_Image"); } When designing a custom class to be used as a data source, the primary consideration is whether the bindable properties provide read-only or read/write access. If they are read-only, the only requirement is that they be public. For properties that can be updated, the class must expose and fire an event to which the binding can subscribe. Recall that the name of this event is propertynameChanged. This event is fired in the Set block of the property (see Listing 12-3). Listing 12-3. Custom Data Source Class
// Bind control to array populated with instances of // custom class public class MyMovie { private string myID; private string myTitle; private int myYear; private string myStudio; private bool myBestPicture; private Image myImage; // public event EventHandler Movie_YearChanged; public event EventHandler StudioChanged; public MyMovie(string id, string title, int year, string studio, bool bp, Image img) { myTitle = title; myYear = year; myStudio = studio; myBestPicture = bp; myImage = img; myID = id; } // Only public properties can be bound to control public string Movie_Title { get { return myTitle; } } // Make read/write so update can occur public int Movie_Year { get { return myYear; } set { myYear = value; if (Movie_YearChanged != null) Movie_YearChanged(this, EventArgs.Empty); } } public string Studio { get { return myStudio; } set { myStudio = value; if (StudioChanged != null) StudioChanged(this, EventArgs.Empty); } } public Image Movie_Image { get { return myImage; } } public bool BestPicture { get { return myBestPicture; } } }
Adding an Item to the Data Source
Clicking the Add Movie button causes information about a single movie to be added to the data source (see Listing 12-4). If the source is a table, a row is added; if an array, an object is created and inserted. An addition to a data table is automatically pushed to the control and made visible. When a custom object is added, the Refresh method of the CurrencyManager must be executed to synchronize the control. Note that Refresh is specific to the CurrencyManager class and not available on BindingManagerBase. Listing 12-4. Add an Item to a Data Source
// Test effects of adding a new item to the data source private void button2_Click(object sender, EventArgs e) { if (ds != null) { // Add a row to the table DataTable dt = ds.Tables[0]; DataRow dRow = dt.NewRow(); dRow["movie_ID"] = 99; dRow["movie_Title"] = "Rear Window"; dRow["movie_Year"] = "1954"; dRow["studio"] = "Paramount"; dRow["BestPicture"] = 0; dRow["afi_rank"] = 42; Image defaultImage = Image.FromFile(@"c:\afi42.gif"); dRow["movie_Image"] = defaultImage; dt.Rows.Add(dRow); } else { Image movieImg = Image.FromFile(@"c:\afi42.gif"); movieList.Add(new MyMovie("42", "Rear Window", 1954, "Paramount", false, movieImg)); // Refresh() is needed to display item in ListBox/ComboBox CurrencyManager cm = (CurrencyManager)this.BindingContext[movieList]; cm.Refresh(); } }
Identifying Updates
The rows in a table have a RowState property that can be used to determine if a value in the row has been changed (discussed in Chapter 11). This method checks the value of that property for each row in the data source table. If the value is DataRowState.Modified, each column in the row is checked to determine which values have changed (see Listing 12-5). This routine can be used to determine whether an update to the original database is necessary. Observe that the method checks only for data changes. You can easily extend it to check for deletions and additions. Listing 12-5. Check Data Source for Any Updates
// Checks status of each row in data table to identify any // changes. This works only when data source is a Data Table. private bool DataIsDirty(DataTable dt){ bool result = false; foreach(DataRow drw in dt.Rows){ // Check all rows in the table for a modified state if(drw.RowState == DataRowState.Modified) { string msg = (string)drw["movie_Title"]+":"; string curr; string orig; // Examine each column in the row for a change foreach(DataColumn col in dt.Columns) { curr= drw[col, DataRowVersion.Current].ToString().Trim(); orig= drw[col, DataRowVersion.Original].ToString().Trim(); if(!curr.Equals(orig) || curr != orig || string.CompareOrdinal(curr,orig) !=0) { msg += "\r\n" + orig + " " + curr; result=true; } } MessageBox.Show(msg); // Display changes in a row } } return result; } Update Original Database with Changes
When the modifiable data source is a data table, the Update method of its associated DataAdapter can be used to flush changes to the database. This topic is discussed in detail in Section 11.4, "DataSets, DataTables, and the Disconnected Model." try { int updates = da.Update(ds, "movies"); MessageBox.Show("Updates: "+updates.ToString()); } catch (Exception ex) { MessageBox.Show(ex.Message); }
|
< Day Day Up > |