DataGrid refresh issue - wpf

I have a Silverlight 4 DataGrid which has its ItemsSource bound to an ObservableCollection. When i modify an element of my ObservableCollection the modified element is correctly displayed inside my grid except the element of one column. This columns differs from the others in the way it is a TemplateColumn and it's using a ValueConverter.
The Template for the column consists of a simple stackPanel that includes a Path control and a Label. And the Label is bound to some Source object with the help of a simple ValueConverter.
The problem now is when i modify some element that belongs to the ObservableCollection all columns of the grid are displayed correctly except the one described above. It simply stays unchanged - but when i use the mousecursor to select the DataGridCell and click it a second time, the desired refresh suddenly happens.
So I guess it's something simple what i am missing here, but I can't find it ...
Thanks in advance ..
EDIT:
In the meanwhile I was able to further locate the problem: It seems that after I modify an element of my ObservableCollection the corresponding ValueConverter that belongs to the label that is in my grid that is bound to the source is simply not called. When i click inside the cell the ValueConverter is getting called as it should. BUT it won't automatically - So how do I achieve that ? please help :)
EDIT:
The binding:
<sdk:Label Content="{Binding Route.Legs, Converter={StaticResource IncomingTableRouteTripConverter}}" Margin="9,0,0,0" Style="{StaticResource TripLabelTemplate}" FontFamily="Arial" FontSize="10.667" Padding="0" Height="10" FontWeight="Bold" />
This is the code of my ValueConverter:
(But I don't think that the code of the converter has anything to do with my problem I only posted it here for completeness)
public override object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture) {
string trip = "";
if (value != null) {
List<Leg> legs = (List<Leg>)value;
if (legs.Count >= 1) {
for (int i = 0; i <= legs.Count - 1; i++) {
trip += ((Leg)legs[i]).Start.ICAO + " - " + ((Leg)legs[i]).Stop.ICAO + " - ";
}
trip = trip.Substring(0, trip.Length - 2);
}
}
return trip;
}

For all nodes in the Path notifications need to be in place, so both the class owning Route and the class owning Legs need to implement INPC.
Further if you add items to the Legs list naturally nothing will be updated, in fact even if the Legs property were of type ObservableCollection<...> that would not matter as the binding engine only cares about INPC.
So if you want the binding to update if the collection changes you need to fire property changed for the Legs property every time it somehow is modified (including a complete replacement of the reference).

IF you use like
Content="{Binding Path=Parameter Converter={StaticResource SomeConverter}}"
then your problem might be solved...

Related

WPF DataGrid with mutable row/cell types - reuse templates

I have a complex table of data (about 150 rows, between 1 and 100 columns) which I want to display and edit using a DataGrid in WPF, but I've hit a big stumbling block. Please forgive me (and correct me) if my terminology is off in points, as I'm quite new to WPF and XAML.
To understand my problem, my requirements are:
The data consists of a variable number of rows and columns which are loaded through AJAX
Every row (class "Record" in my test implementation) has a few fixed properties that need to be displayed as well as a varying number of properties (though all rows have the same number of such properties) in a collection
Each row/Record has an type (e.g. String, Integer, Boolean) for its properties inferred through an Enum property "VType". Properties should be displayed and edited with a template according to the VType value.
Columns may be added or removed at run time
(Some) rows may also be added or removed at run time
Rows can change their "type" at run time
So far, I've built a working example with DataGridTextColumns that creates the columns from simulated data and fills the bound collection. I've implemented INotifyPropertyChanged and used ObservableCollections where necessary, so reactivity works, and my propoerty values are pulled from the binding to the individual property and correctly shown.
When adding the columns, I passed the correct binding. For my example app, I use the column index to bind each column to the correct Property object in the Record's collection:
// Amounts to "Properties[0].Value", "Properties[1].Value", etc.
var binding = new Binding(string.Format("Properties[{0}].Value", column.Index));
dataGrid.Columns.Add(new DataGridTextColumn() { Header = column.Name, Binding = binding });
Now I tried to tackle using different templates for different "Record types", i.e. a vType property in my Record class. I've created data templates in Window.Resources (very crude ones to start), set up a lookup and implemented the RecordTemplateSelector:
<!--BOOL TEMPLATE-->
<DataTemplate x:Key="booleanTemplate">
<CheckBox IsChecked="{Binding Value}" Background="LightGray" Margin="5, 0, 0, 0"/>
</DataTemplate>
<!--STRING TEMPLATE-->
<DataTemplate x:Key="stringTemplate">
<TextBlock Text="{Binding Value}"/>
</DataTemplate>
<!--INTEGER TEMPLATE-->
<DataTemplate x:Key="integerTemplate">
<TextBlock Text="{Binding Value}"/>
</DataTemplate>
<local:RecordTemplateSelector x:Key="myRecordTemplateSelector"
BooleanTemplate="{StaticResource booleanTemplate}"
StringTemplate="{StaticResource stringTemplate}"
IntegerTemplate="{StaticResource integerTemplate}"/>
And this is my TemplateSelector:
class RecordTemplateSelector : DataTemplateSelector
{
public DataTemplate StringTemplate { get; set; }
public DataTemplate BooleanTemplate { get; set; }
public DataTemplate IntegerTemplate { get; set; }
public override DataTemplate SelectTemplate(object item, DependencyObject container)
{
var selectedTemplate = StringTemplate;
var record = item as Record;
if (item == null) return selectedTemplate;
switch (record.VType)
{
case Record.ValueType.Checkbox:
selectedTemplate = BooleanTemplate;
break;
case Record.ValueType.Integer:
selectedTemplate = IntegerTemplate;
break;
case Record.ValueType.String:
selectedTemplate = StringTemplate;
break;
}
return selectedTemplate;
}
}
It does pull the correct template, but when I thought I had it working, I noticed that I can't correctly bind my DataGridTemplateColumn - it is always implicitly bound to the whole row (i.e. Record object) and I don't see a way how my template can know which element in the Record's Property collection it should apply to.
I'm at a loss where to go from here. Is there a way to inherit the column's binding down to the template? Is there some other way to pass the correct item (an index would be okay too) to the template? Or do I have to use a completely different approach?
Big Thanks in advance for any input you can give me.

When does a ComboBox receive its Items if it is bound to ObservableCollection?

I am attempting a save/load mechanism for re-use in a business application. I have the groundwork laid to read/write ObservableCollection<> to/from xml, using attributes to describe my class properties. That part is working. I can save an ObservableCollection to XML, then load the XML back into an ObservableCollection the next time I run the program.
Here's my problem. I have a ComboBox whose ItemsSource.DataContext = ObservableCollection<Flag>;
When I run the program, it accepts the binding just fine, but the ComboBox itself does not populate itself until later. I want to set the SelectedItem to be the first item in the ObservableCollection<Flag> that I have loaded from XML. Nothing happens though, because as the program is executing it's startup methods, the Items.Count remains 0. I'm guessing the ComboBox doesn't populate itself until it gets focus. How do I work around this? Can I force the ComboBox to populate itself? I've tried cb_ARDAR_ARFlag.Items.Refresh();
XAML:
<ComboBox Name="cb_ARDAR_ARFlag"
ItemsSource="{Binding}"
SelectionChanged="cb_ARDAR_ARFlag_SelectionChanged">
<ComboBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Flag_Desc}"/>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
Relevant Code:
public MainWindow()
{
InitializeComponent();
setDataBinding();
loadSavedData();
}
private void setDataBinding()
{
//Returns ObservableCollection<Flag>
cb_ARDAR_ARFlag.DataContext = Flag.getOCAvailableFlags();
}
private void loadSavedData()
{
//When it gets here the ItemCount is 0 so nothing happens.
//Refresh didn't help
cb_ARDAR_ARFlag.Items.Refresh();
Flag f = Enforcement_Save.loadOCARFlag().First();
cb_ARDAR_ARFlag.SelectedItem = f;
}
At this point I'm still not sure the code at the end will successfully identify the correct 'flag' item to be selected, or if I'll end up using Linq. Which, by the way, leads me to another question. Can you Linq to ComboBox.Items somehow?
I have recreated your issue, and your are correct, the items count is = 0 in the loadSavedData method. The combobox doesn't seem to be populated until after the constructor has fully executed.
In the meantime I found you can use the ItemsSource property to load the combobox at the time you want it loaded:
cb_ARDAR_ARFlag.ItemsSource = Flag.getOCAvailableFlags();

WPF: Adapting to changes in size by changing level of details

Lets say that a view contains a list of customers. By default, this view is given a lot of screen estate, and fill it with details of the customer. Each item in the list could display the name of the customer in a larger font, the address on a second line with a smaller font. Maybe some statistics like total of previous orders etc.
Now, if the user narrows the window, it want be enough space for all that details. What would be the right way of dealing with this? Is there some way of binding what datatemplate is used for each item?
Now, if the user makes the window even smaller - would it be possible to get rid of the list all together? Replacing it with a label showing the count of customers or something?
Any suggestions on how this could be solved?
Do you know of any demoes demonstrating anything similar?
I would approach this by binding the Visibility property of your controls to the width (or height, depending on your layout) of the window, via a converter. Consider something like this:
public class HideIfSmallConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
var c = value as IComparable;
if (c == null) return Visibilty.Visible;
return c.CompareTo(parameter) < 0 ? Visibility.Collapsed : Visibility.Visible;
}
}
Now we have a comparer that will let us collapse an element if the value is less than the given parameter. We can use that in this way:
<ListBox Visibility="{Binding ActualWidth,RelativeSource={RelativeSource FindAncestor,AncestorType=Window},Converter={StaticResource hideIfSmall},ConverterParameter=400}" />
So the idea is that the ListBox collapses if the window's width drops below 400.
None of this is tested, but hopefully it gives you some ideas.

Select the "old value" in a listbox in a edit page

In my app I have a Save / Edit page.
The current flow is the following: the user has a main page, with a list of elements. He can click on the "add" button, it goes to an "Add" page, in which he can enter information and store it. Once he does it, the information is saved and shown in the list.
If he clicks in the list, he moves to an "Edit" page, in which he can change the information.
In reality, the Add and the Edit page are the same, the second has the fields populated while the first hasn't.
I have 3 listboxes in this page, one for severities, one for categories and one for reporter. This information is selected in the listbox before saving, and in the edit phase, it should be selected automatically, so the user knows the "old" value.
To select the values automatically, I tried two approaches:
1-In my xaml:
<ListBox Height="103" Name="lbSeverities" Width="439" HorizontalAlignment="Left" Margin="20,0,0,0" SelectionMode="Single" ItemsSource="{Binding Severities}" DisplayMemberPath="Name" SelectedItem="{Binding Task.Severity}"/>
And I also override the Equals method of the Severity Class to a reasonable implementation.
2- In my xaml
<ListBox Height="103" Name="lbSeverities" Width="439" HorizontalAlignment="Left" Margin="20,0,0,0" SelectionMode="Single" ItemsSource="{Binding Severities}" DisplayMemberPath="Name" SelectedIndex="{Binding Task.Severity, Converter={StaticResource SeverityToIndexConverter}}"/>
And I created the SeverityToIndexConverter with this code:
public class SeverityToIndexConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
if (value != null && value is Severity)
{
Severity currentSeverity = (Severity)value;
for (int i = 0; i < (App.Current as App).MainViewModel.Severities.Count; i++)
{
Severity sev = (App.Current as App).MainViewModel.Severities[i];
if (currentSeverity.ID == sev.ID)
return i;
}
}
return -1;
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
Both of them presented the same results: sometimes the values are automatically selected, but sometimes not. It is very unstable when it is selected.
I imagined about some exception, tried to catch it, but I do not get anything.
I also tried to debug, I noticed that in case 1, the equals method is called in parallel for all members of the collection, so I tried the second approach. Debugging didn't lead me to any answer.
Has anyone faced a similar situation?
What can I do to make the Listbox value to be selected when the user enters in the "Edit" Page?
Thanks,
Oscar
I do the same thing. My xaml looks like your first example.
Here's what I'd do:
Create a new property called Severity. In the getter return Task.Severity. Bind the listbox SelectedItem to the new property.
It may also be a timing thing so you may have to call NotifyPropertyChanged on the new property once the listbox has loaded its list.

What's the simplest way to display NULL values as "NULL" with WPF Data Binding?

I have this legacy database for which I'm building a custom viewer using Linq to Sql.
Now some of the fields in a table can have the value NULL.
Using normal databinding in a DataTemplate (typed for the Class generated by the ORM Designer)
<TextBlock Text="{Binding Path=columnX}"/>
If columnX has value NULL, nothing is displayed. (It seems the to be the WPF convention) I'd like to display "NULL" instead if the value is NULL. (equivalent to column_value ?? "NULL")
I could use a converter as in
<TextBlock Text="{Binding Path=columnX, Converter={StaticResource nullValueConverter}}"/>
Converter class
class NullValueConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
if (value == null)
return "NULL";
...
But this seems like too much work. Also this logic would need to be duplicated in existing non-trivial converters..
Is there a quick-hit way to accomplish this?
The binding class has a property called TargetNullValue that can be used to substitute something else if the binding returns a NULL value. Your example becomes:-
<TextBlock Text="{Binding Path=columnX, TargetNullValue=My Substitute Text}"/>
There is another property of the Binding class that is also useful called FallbackValue. This would be the substitute value to use if the binding expression cannot be resolved (i.e. not found), e.g for when the path you use (columnX in your example) is not a member of the data context or source.
Update(Gishu): Requires .NET Framework 3.5 SP1 a 53MB download. Without it, the above code won't compile. TargetNullValue is a new addition to the Binding class.
Right click on the DBML file, click View code. Add a partial class for the table you want to work with. Add a property returning value ?? null or something like that. The trick is that LINQ to SQL declares the classes as partial, so you can easily extend them.
I don't think there's a cleaner way to do it. You could possibly use a DataTrigger in a style template to pick up on null values, but data triggers can be a bit "funny" when using NULLs so you'd likely need a converter anyway.

Resources