Silverlight data binding in the code behind - silverlight

I am doing something like this in a Silverlight 3 datagrid:
for (int x = 0; x < ThisForecast.Periods.Count; x++)
{
var TextColumn = new DataGridTextColumn();
TextColumn.Header = ThisForecast.Periods[x].Name;
TextColumn.Binding = new Binding(String.Format("Periods[{0}].Quantity", x));
TextColumn.Binding.Mode = BindingMode.TwoWay;
TextColumn.IsReadOnly = false;
dgItemForecast.Columns.Add(TextColumn);
}
And it works great, but I want to change the ready only to something more like:
TextColumn.IsReadOnly = new Binding(String.Format("Periods[{0}].IsReadOnly", x));
And while it seems easy to do in XAML, I can't figure out the correct method to do this in the code behind. Obviously I can't set it to a 'binding', but where would I be able to set something like that?
EDIT #1:
I looked at the BindingOperations.SetBinding() given below, but could not find a DependencyProperty for IsReadOnly. Is there a way to inject/add one?

BindingOperations.SetBinding(textColumn, DataGridTextColumn.IsReadOnlyProperty, new Binding(...));

Related

How to display Object data in a TableLayoutPanel in a performant way

I have written the folowing code to populate the data from a object in a tablelayoutpanel control. It works Smile | :) , but when its loading the data onto the table, it flickers/jumps for few seconds and then after 2-3 seconds when its done processing the data it populates the data Frown | :( . I believe this behaviour is because of the code written for dynamically processing & drawing of the various controls in the table based on the object data.
I need your help in optimising the code/improving the performance of this code so that the table can load smoothly and fast. Please help. Thanks.
PS: This code is written for a table containing small amount of data. But going forward the same is planned for populating table with 4X more data. If this is the case, then performance will be very poor, which worries me. Please suggest some ideas.
private void button1_Click(object sender, EventArgs e)
{
Common obj = new Common();
obj.CreateDeserializedXmlObject(#"E:\TestReport.xml");
var v = obj.GetAdminData();
tableLayoutPanel1.ColumnCount = 4;
tableLayoutPanel1.RowCount = ((v.DOCREVISIONS.Length * 4) + 1 + (v.USEDLANGUAGES.L10.Length));
Label labelLanguage = new Label();
Label labelUsedLanguage = new Label();
Label labelDocRevisions = new Label();
labelLanguage.Text = "Language:";
labelUsedLanguage.Text = "Used Language:";
labelDocRevisions.Text = "Doc-Revisions:";
ComboBox comboBoxLanguage = new ComboBox();
comboBoxLanguage.Items.Add(v.LANGUAGE.Value.ToString());
comboBoxLanguage.SelectedIndex = 0;
ComboBox comboBoxUsedLanguage = new ComboBox();
foreach (LPLAINTEXT Lang in v.USEDLANGUAGES.L10)
{
comboBoxUsedLanguage.Items.Add(Lang.L.ToString());
}
comboBoxUsedLanguage.SelectedIndex = 0;
int index = 0;
Label[] labelDocRevision = new Label[v.DOCREVISIONS.Length];
Label[] labelRevision = new Label[v.DOCREVISIONS.Length];
Label[] labelState = new Label[v.DOCREVISIONS.Length];
Label[] labelTeamMember = new Label[v.DOCREVISIONS.Length];
Label[] labelDate = new Label[v.DOCREVISIONS.Length];
TextBox[] textBoxRevision = new TextBox[v.DOCREVISIONS.Length];
TextBox[] textBoxState = new TextBox[v.DOCREVISIONS.Length];
TextBox[] textBoxTeamMember = new TextBox[v.DOCREVISIONS.Length];
TextBox[] textBoxDate = new TextBox[v.DOCREVISIONS.Length];
foreach (DOCREVISION dcr in v.DOCREVISIONS)
{
labelDocRevision[index] = new Label();
labelRevision[index] = new Label();
labelState[index] = new Label();
labelTeamMember[index] = new Label();
labelDate[index] = new Label();
textBoxRevision[index] = new TextBox();
textBoxState[index] = new TextBox();
textBoxTeamMember[index] = new TextBox();
textBoxDate[index] = new TextBox();
labelDocRevision[index].Text = "DOCREVISION["+index.ToString()+"]:";
labelRevision[index].Text = "Revision:";
labelState[index].Text = "State:";
labelTeamMember[index].Text = "TeamMemberRef:";
labelDate[index].Text = "Date:";
textBoxRevision[index].Text = dcr.REVISIONLABEL.Value.ToString();
textBoxState[index].Text = dcr.STATE.Value.ToString();
textBoxTeamMember[index].Text = dcr.TEAMMEMBERREF.Value.ToString();
textBoxDate[index].Text = dcr.DATE.Value.ToString();
index++;
}
// Add child controls to TableLayoutPanel and specify rows and column
tableLayoutPanel1.Controls.Add(labelLanguage, 0, 0);
tableLayoutPanel1.Controls.Add(labelUsedLanguage, 0, 1);
tableLayoutPanel1.Controls.Add(labelDocRevisions, 0, 2);
tableLayoutPanel1.Controls.Add(comboBoxLanguage, 1, 0);
tableLayoutPanel1.Controls.Add(comboBoxUsedLanguage, 1, 1);
int docRevRowSpacing = 2;
for (int loop = 0; loop < index; loop++)
{
tableLayoutPanel1.Controls.Add(labelDocRevision[loop], 1, docRevRowSpacing);
tableLayoutPanel1.Controls.Add(labelRevision[loop], 2, docRevRowSpacing);
tableLayoutPanel1.Controls.Add(labelState[loop], 2, docRevRowSpacing+1);
tableLayoutPanel1.Controls.Add(labelTeamMember[loop], 2, docRevRowSpacing+2);
tableLayoutPanel1.Controls.Add(labelDate[loop], 2, docRevRowSpacing+3);
tableLayoutPanel1.Controls.Add(textBoxRevision[loop], 3, docRevRowSpacing);
tableLayoutPanel1.Controls.Add(textBoxState[loop], 3, docRevRowSpacing+1);
tableLayoutPanel1.Controls.Add(textBoxTeamMember[loop],3 , docRevRowSpacing+2);
tableLayoutPanel1.Controls.Add(textBoxDate[loop], 3, docRevRowSpacing+3);
docRevRowSpacing += 4;
}
tableLayoutPanel1.CellBorderStyle = TableLayoutPanelCellBorderStyle.Single;
Controls.Add(this.tableLayoutPanel1);
}
There are two minor changes that helps a little bit.
At the start of your code you can call SuspendLayout. This prevents the TableLayoutPanel to redraw itself every time you add a control to it. When you're done adding all controls at the end you call ResumeLayout. At that moment the TableLayoutPanel will redraw only once. It still takes time but at least most the flickering is gone. At the end of your example code you add the tableLayoutPanel1 again to the forms control collection. If the TableLayoutPanel is on your form designer you don't need that and by doing it you make your performance worse because now you have two tableLayoutPanels that need to be painted.
private void button1_Click(object sender, EventArgs e)
{
tableLayoutPanel1.SuspendLayout();
// all your other code goes here
// not sure why you add the tableLayouyPanel AGAIN to the
// form control collection.
// Controls.Add(this.tableLayoutPanel1);
tableLayoutPanel1.ResumeLayout();
}
I noticed in my testing that resizing the form gives the same flickering effect. I used the ResizeBegin and ResizeEnd events to do the same Suspend and Resume layout trick:
private void Form1_ResizeBegin(object sender, EventArgs e)
{
tableLayoutPanel1.SuspendLayout();
}
private void Form1_ResizeEnd(object sender, EventArgs e)
{
tableLayoutPanel1.ResumeLayout();
}
This as much as you can do with your current code (except maybe the use of all those arrays with controls but their overhead is not the major issue here).
The TableLayoutPanel is maybe not the best control for what you want to achieve. It lacks for example VirtualMode support, something the DataGridView does. That would enable you to only load and show data that is visible on the form (and therefor create controls for it). Adapting your code to use that control is left as an exercise for the reader and if new issues pop-up feel free to start a new question.

Can't get two-way binding to work :)

I have a project which sets up dynamic tabs for monitoring and thus hosts several "identity" objects which are INotifyPropertyChanged and work individually as tested in a dummy app. However, I can't get the bindings to work correctly in the app I'm working on. They are set up i.e.:
(Configuration.Identities is an ObservableCollection<Identity>)
foreach (Identity id in Configuration.Identities)
{
workTab = new TabItem();
workSettingsTab = new SettingsTabItem();
workTab.DataContext = id;
Binding workTabHeaderBinding = new Binding("Username");
workTabHeaderBinding.Converter = new ToLowerConverter();
workTab.SetBinding(TabItem.HeaderProperty, workTabHeaderBinding);
workSettingsTab.DataContext = id;
Binding workSettingsTabHeaderBinding = new Binding("Username");
workSettingsTabHeaderBinding.Converter = new ToUpperConverter();
workSettingsTab.SetBinding(TabItem.HeaderProperty, workSettingsTabHeaderBinding);
This stuff above works fine, presumably because it's one-way. But these two only work one-way:
workSettingsTab.txtUsername.DataContext = id;
Binding usernameBinding = new Binding("Username");
usernameBinding.Mode = BindingMode.TwoWay;
workSettingsTab.txtUsername.SetBinding(TextBox.TextProperty, usernameBinding);
Binding getJournals = new Binding("LoadJournals");
scrapeJournals.Mode = BindingMode.TwoWay;
workSettingsTab.chkScrapeJournals.DataContext = id;
workSettingsTab.chkScrapeJournals.SetBinding(CheckBox.IsCheckedProperty, scrapeJournals);
I noticed when debugging my save-routine, that the Identity object referenced in DataContext in above controls does indeed contain the changed values, but the one in the ObservableCollection I used in the foreach above is not updated. So somehow the Identity object gets copied from the one initially used to set the DataContext property during the foreach. What am I doing wrong here?
Right. I can't get this to make sense, but so far:
ObservableCollection<SwitchClass> col2 = new ObservableCollection<SwitchClass>();
col2.Add(new SwitchClass{theSwitch=false});
col2.Add(new SwitchClass{theSwitch=true});
col2.Add(new SwitchClass{theSwitch=false});
col2.Add(new SwitchClass{theSwitch=true});
customTab cTab;
Int32 z = 0;
Binding b;
foreach (SwitchClass s in col2)
{
cTab = new customTab();
cTab.theCheckbox.DataContext = s;
b = new Binding("theSwitch");
b.Mode = BindingMode.TwoWay;
cTab.SetBinding(CheckBox.IsCheckedProperty, b);
cTab.Header = "Tab " + z.ToString();
z++;
tabControl.Items.Add(cTab);
}
Does not work, but:
ObservableCollection<SwitchClass> col2 = new ObservableCollection<SwitchClass>();
col2.Add(new SwitchClass{theSwitch=false});
col2.Add(new SwitchClass{theSwitch=true});
col2.Add(new SwitchClass{theSwitch=false});
col2.Add(new SwitchClass{theSwitch=true});
customTab cTab;
Int32 z = 0;
foreach (SwitchClass s in col2)
{
cTab = new customTab();
cTab.DataContext = s;
cTab.Header = "Tab " + z.ToString();
z++;
tabControl.Items.Add(cTab);
}
with
<CheckBox Name="theCheckbox" Width="200" Height="50" Content="Checkbox" IsChecked="{Binding theSwitch}" />
works perfectly. I suppose this is just a limitation of WPF?

How can I get items from current page in PagedCollectionView?

I've got my objects in PagedCollectionView bound to DataGrid and DataPager.
var pcView = new PagedCollectionView(ObservableCollection<Message>(messages));
How can I easily get items from current page in PagedCollectionView from my ViewModel? I wish there were something like this:
var messagesFromCurrentPage = pcView.CurrentPageItems; // error: no such a property
There are properties like SourceCollection, PageIndex and Count but I don't find them useful in this case. What am I missing here?
If you want to get select items you can just use Linq to do it.
var items = pcView.Where(i => i.SomeCondition == true);
Make sure you add a using statement for System.Linq.
Edit: Whenever I have a question as to what is really going on I just look at the code using Reflector (or ILSpy). In this case here is the relevant code inside GetEnumerator() which is how the Select or Where gets the items in the list:
List<object> list = new List<object>();
if (this.PageIndex < 0)
{
return list.GetEnumerator();
}
for (int i = this._pageSize * this.PageIndex; i < Math.Min(this._pageSize * (this.PageIndex + 1), this.InternalList.Count); i++)
{
list.Add(this.InternalList[i]);
}
return new NewItemAwareEnumerator(this, list.GetEnumerator(), this.CurrentAddItem);
So you can see how it is returning only the items in the current page from this code.

How to maintain ComboBox.SelectedItem reference when DataSource is resorted?

This really seems like a bug to me, but perhaps some databinding gurus can enlighten me? (My WinForms databinding knowledge is quite limited.)
I have a ComboBox bound to a sorted DataView. When the properties of the items in the DataView change such that items are resorted, the SelectedItem in my ComboBox does not keep in-sync. It seems to point to someplace completely random. Is this a bug, or am I missing something in my databinding?
Here is a sample application that reproduces the problem. All you need is a Button and a ComboBox:
public partial class Form1 : Form
{
private DataTable myData;
public Form1()
{
this.InitializeComponent();
this.myData = new DataTable();
this.myData.Columns.Add("ID", typeof(int));
this.myData.Columns.Add("Name", typeof(string));
this.myData.Columns.Add("LastModified", typeof(DateTime));
this.myData.Rows.Add(1, "first", DateTime.Now.AddMinutes(-2));
this.myData.Rows.Add(2, "second", DateTime.Now.AddMinutes(-1));
this.myData.Rows.Add(3, "third", DateTime.Now);
this.myData.DefaultView.Sort = "LastModified DESC";
this.comboBox1.DataSource = this.myData.DefaultView;
this.comboBox1.ValueMember = "ID";
this.comboBox1.DisplayMember = "Name";
}
private void saveStuffButton_Click(object sender, EventArgs e)
{
DataRowView preUpdateSelectedItem = (DataRowView)this.comboBox1.SelectedItem;
// OUTPUT: SelectedIndex = 0; SelectedItem.Name = third
Debug.WriteLine(string.Format("SelectedIndex = {0:N0}; SelectedItem.Name = {1}", this.comboBox1.SelectedIndex, preUpdateSelectedItem["Name"]));
this.myData.Rows[0]["LastModified"] = DateTime.Now;
DataRowView postUpdateSelectedItem = (DataRowView)this.comboBox1.SelectedItem;
// OUTPUT: SelectedIndex = 2; SelectedItem.Name = second
Debug.WriteLine(string.Format("SelectedIndex = {0:N0}; SelectedItem.Name = {1}", this.comboBox1.SelectedIndex, postUpdateSelectedItem["Name"]));
// FAIL!
Debug.Assert(object.ReferenceEquals(preUpdateSelectedItem, postUpdateSelectedItem));
}
}
To clarify:
I understand how I would fix the simple application above--I only included that to demonstrate the problem. My concern is how to fix it when the updates to the underlying data rows could be happening anywhere (on another form, perhaps.)
I would really like to still receive updates, inserts, deletes, etc. to my data source. I have tried just binding to an array of DataRows severed from the DataTable, but this causes additional headaches.
Just add a BindingContext to the ComboBox :
this.comboBox1.DataSource = this.myData.DefaultView;
this.comboBox1.BindingContext = new BindingContext();
this.comboBox1.ValueMember = "ID";
this.comboBox1.DisplayMember = "Name";
By the way, try not keeping auto-generated names for your widgets (comboBox1, ...), it is dirty. :-P
The only promising solution I see at this time is to bind the combo box to a detached data source and then update it every time the "real" DataView changes. Here is what I have so far. Seems to be working, but (1) it's a total hack, and (2) it will not scale well at all.
In form declaration:
private DataView shadowView;
In form initialization:
this.comboBox1.DisplayMember = "Value";
this.comboBox1.ValueMember = "Key";
this.shadowView = new DataView(GlobalData.TheGlobalTable, null, "LastModified DESC", DataViewRowState.CurrentRows);
this.shadowView.ListChanged += new ListChangedEventHandler(shadowView_ListChanged);
this.ResetComboBoxDataSource(null);
And then the hack:
private void shadowView_ListChanged(object sender, ListChangedEventArgs e)
{
this.ResetComboBoxDataSource((int)this.comboBox1.SelectedValue);
}
private void ResetComboBoxDataSource(int? selectedId)
{
int selectedIndex = 0;
var detached = new KeyValuePair<int, string>[this.shadowView.Count];
for (int i = 0; i < this.shadowView.Count; i++)
{
int id = (int)this.shadowView[i]["ID"];
detached[i] = new KeyValuePair<int, string>(id, (string)this.shadowView[i]["Name"]);
if (id == selectedId)
{
selectedIndex = i;
}
}
this.comboBox1.DataSource = detached;
this.comboBox1.SelectedIndex = selectedIndex;
}
Must detach event handler in Dispose:
this.shadowView.ListChanged -= new ListChangedEventHandler(shadowView_ListChanged);
Your example sorts the data on the column it updates. When the update occurs, the order of the rows changes. The combobox is using the index to keep track of it's selected items, so when the items are sorted, the index is pointing to a different row. You'll need to capture the value of comboxBox1.SelectedItem before updating the row, and set it back once the update is complete:
DataRowView selected = (DataRowView)this.comboBox1.SelectedItem;
this.myData.Rows[0]["LastModified"] = DateTime.Now;
this.comboBox1.SelectedItem = selected;
From an architecture perspective, the SelectedItem must be cleared when rebinding the DataSource because the DataBinder don't know if your SelectedItem will persist or not.
From a functional perspective, the DataBinder may not be able to ensure that your SelectedItem from you old DataSource is the same in your new DataSource (it can be a different DataSource with the same SelectedItem ID).
Its more an application feature or a custom control feature than a generic databinding process.
IMHO, you have theses choices if you want to keep the SelectedItem on rebind :
Create a reusable custom control / custom DataBinder with a persistance option which try to set the SelectedItem with all your data validation (using a DataSource / item identification to ensure the item validity)
Persist it specifically on your Form using the Form/Application context (like ViewState for ASP.NET).
Some controls on the .NET market are helping you by rebinding (including selections) the control from their own persisted DataSource if the DataSource is not changed and DataBind not recalled. That's the best pratice.

Silverlight programmatically binding from C# code

I have a Canvas and a custom control called BasicShape
After I add two BasicShape controls on the Canvas, I want programatically to connect them with a Line and I want to do this using the Binding class.
I want to connect the Bottom side of first shape with the Top side of the second one.
Initially i tried to connect only the X1 property of the Line with the Canvas.Left attached property of the fisrt BasicShape but this doesn't work. Line X1 property is not updated when I change the Canvas.SetLeft(basicShape1) value
BasicShape bs1 = canvas.Children[0] as BasicShape;
BasicShape bs2 = canvas.Children[1] as BasicShape;
Line line = new Line();
line.StrokeThickness = 1;
line.Stroke = new SolidColorBrush(Colors.Red);
line.X1 = 100;
line.Y1 = 100;
line.X2 = 200;
line.Y2 = 200;
canvas.Children.Add(line);
Binding b = new Binding("AnyName");
b.Source = bs1;
b.Path = new PropertyPath(Canvas.LeftProperty);
line.SetBinding(Line.X1Property, b);
I'm trying to create a simple UML diagram like this one
alt text http://www.invariant-corp.com/omechron/images/uml_diagram.gif
I just did it other way, without binding
This will be a permanent link
http://calciusorin.com/SilverlightDiagrams/
I decided to manually update all lines on shape Location or Size changed
private void basicShape_BasicShapeLocationSizeChangedEvent(BasicShape sender)
{
foreach (CustomLine customLine in lines)
{
if (customLine.StartFromShape(sender))
{
Point point = sender.GetLinePoint(customLine.GetStartSide());
customLine.SetStartPoint(point);
}
if (customLine.EndInShape(sender))
{
Point point = sender.GetLinePoint(customLine.GetEndSide());
customLine.SetEndPoint(point);
}
}
}
I am sure that the Binding solution is more elegant. Anyone interested in my solution, with SL Controls that can be resized, connected with lines, just contact me.

Resources