Silverlight Gridcolumn Collapse Visibility in code - silverlight

I'm having a small problem.
I have this grid with a column:
<data:DataGrid ColumnHeaderStyle="{StaticResource headerStyle}" Foreground="#234BC3" AutoGenerateColumns="False" Name="protocollenBAMDataGrid" LoadingRow="myDataGrid_LoadingRow" SelectionChanged="DataGrid_SelectionChanged">
<data:DataGrid.Columns>
<data:DataGridTemplateColumn Header="Resend" x:Name="ResendColumn">
<data:DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<Button Content="Resend" Tag="{Binding MsgID}" Foreground="#234BC3" Click="dataGridHL7_Click"></Button>
</DataTemplate>
</data:DataGridTemplateColumn.CellTemplate>
</data:DataGridTemplateColumn> ...
If i set the visibility of the "ResendColumn" to collapsed, it works fine.
However I need to do this in code, so I tried doing this in the loading event:
ResendColumn.Visibility = Visibility.Collapsed;
And it gives me a nullref exception:
Object reference not set to an
instance of an object.
I don't understand why?

I get the same behaviour in SL 4. I guess it is related to the column not being a UIElement and thus instantiated by the DataGrid itself (not by the usual stuff happening in InitializeComponent).
I had to access the columns through the Columns property of the DataGrid. And there, setting the visibility works.

Related

Editable DataGrid - CanUserAddRows="True" not working

I have the following DataGrid:
<DataGrid ItemsSource="{Binding EmployeeList}" CanUserAddRows="True" AutoGenerateColumns="False" Margin="0,0,0,90">
<DataGrid.Columns>
<DataGridTemplateColumn Header="CountryCombo2">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<ComboBox ItemsSource="{Binding Path=DataContext.CountryList, RelativeSource={RelativeSource AncestorType={x:Type Window}}}"
DisplayMemberPath="CountryName"
SelectedItem="{Binding EmployeeCountry, Mode=TwoWay}"
SelectedValue="{Binding EmployeeCountry.CountryId}"
SelectedValuePath="CountryId" />
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
</DataGrid>
However, I am unable to add new rows to the DataGrid. Please let me know if I need to provide any additional code.
Update :
Screen 1 : This is the screenshot when the window is just loaded with the hardcoded property values. Now I see the empty new row.
Screen 2 : Here I have added data into the new row with values Rambo and Russia. Now, no matter what I do (tab-out, click in another cell), the next new row is not added. I believe it should be adding a new row.
Screen 3 : Here the newly added row values have disappeared. That is because I double clicked on the thin border between the two empty cells. Now this is pretty weird.
Screen 4 : Now when I click on the Peter cell, the previously entered row data is back but now it is pushed down and a new empty row is inserted before it. This is very strange.
Can anyone please help me understand this behavior of the DataGrid?
In my case,
First ensure your ItemSource is not using an array that can't add new item to it,
use something like List that can add newItem,
Besides, the SomeClass should have an default constructor takes no parameters like
List<SomeClass>();
public Class SomeClass
{
public SomeClass() { }
}
then the new empty row appear in the bottom of the DataGrid.
Refer to this answer.
I'm going ahead and posting this as an answer here as I need to post a code sample and the comments are starting to become extended (got the invite-to-chat message).
The answer to the original question was ensure that the Type T of the ItemsSource had a parameterless constructor.
Try this code attached to the DataGrid's BeginningEdit event to swallow up the cell border clicks:
private void Grid_BeginningEdit(object sender, DataGridBeginningEditEventArgs e)
{
//// Have to do this in the unusual case where the border of the cell gets selected
e.Cancel = true;
}
If you are actually using this handler for something else, or intend to, you can check the OriginalSource to see if it is a Border and cancel the event on that condition.
Use DataGridTextColumn and DataGridComboBoxColumn instead DataGridTemplateColumn, then rows will be added by adequately.
If you want to use DataGridTemplateColumn, then set not only the CellTemplate, but CellEditingTemplate. For example:
<DataGridTemplateColumn Header="Pick a Date">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBlock Text="{Binding myDate}" />
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
<DataGridTemplateColumn.CellEditingTemplate>
<DataTemplate>
<DatePicker SelectedDate="{Binding myDate}" />
</DataTemplate>
</DataGridTemplateColumn.CellEditingTemplate>

DataGridColumnHeader of a DataGrid defined in a DataTemplate for a ViewModel

I have successfully used ProxyElement to pass Data Context of my Data Grid to DataGridColumnHeaders. However, I am trying out something new and I just can't figure out what I am doing wrong over here.
Here is what I am trying to do: I am creating a UserControl and associating it to my ViewModel in my Resources file (see Resources.xaml code snippet below).
Resources.xaml:
<ResourceDictionary
xmlns:myVm="clr-namespace:..."
xmlns:myUserControl="clr-namespace:...">
<DataTemplate DataType={x:Type myVm:DummyModel}">
<myUserControl:DummyUserControl />
</DataTemplate>
</ResourceDictionary>
Now in my UserControl, I have a DataGrid with a DataGridComboBoxColumn. I am trying to access my data context to set its item source and in the past I was able to do it using proxy element. This time however I am not able to do so. (See DummyUserControl.xaml code snippet below)
DummyUserControl.xaml:
<UserControl x:Class="Client.MyControl.DummyUserControl"
...>
<UserControl.Resources>
<FrameworkElement x:Key="ProxyElement" x:Name="ProxyElement"
DataContext="{Binding}" />
</UserControl.Resources>
<DataGrid AutoGenerateColumns="False" ItemsSource="{Binding Products}">
<DataGridComboBoxColumn
Header="Company"
ItemsSource="{Binding Path=DataContext.ProductCompanies,
Source={StaticResource ProxyElement}}"
DisplayMemberPath="Name" SelectedValuePath="Id"
SelectedValueBinding="{Binding CompanyId}" />
</DataGrid>
</UserControl>
When I do this, my binding fails with the following message:
System.Windows.Data Error: 3 : Cannot find element that provides DataContext.
BindingExpression:(no path); DataItem=null; target element is 'FrameworkElement'
(Name='ProxyElement'); target property is 'DataContext' (type 'Object')
I have no idea what to do here. I remember reading that the datacontext for a datatemplate is automatically set, so I have no idea why the data context is null in this case. To prove it is null, I also tried setting the binding in the code-behind file and added a breakpoint to check its value (which was null).
Can anyone suggest what to do here?
Edit 1
I have also tried the following approaches:
Remove ProxyElement altogether and see if it can detect DataContext. To no surprise, this failed.
Tried binding to the templated parent. Fail.
Tried binding to the UserControl itself. Fail.
I also tried referencing the data context of the parent item where this view model is going to be displayed, which is in a TabItem of a TabControl.
All of the alternate bindings gave me same error as the error above.
Here is a (working but not preferred) solution to this problem. You will realize why it is not preferred by the end of it.
The key to this problem is understanding what and how data context of a data template works. Whenever you define a Data Template for a View Model, the data context for the view that follows, whether it is a user control or just xaml itself, is the View Model! This shouldn't be a surprise to anyone.
But this will surprise people: if you specify a User Control, the Data Context of the User Control is not set during construction of the User Control! In other words, in the constructor of User Control, Data Context is going to be null. Furthermore, any XAML code that relies on the Data Context at construction time, which in this case was my FrameworkElement resource called ProxyElement got its DataContext set to null because it gets constructed at construction time of the User Control!
So when does the DataContext get set? Simply after the User Control is created. In pseudo code, this following describes the logic behind drawing a ViewModel:
Draw ViewModel x;
DataTemplate in ResourceDictionary says ViewModel x can be drawn using UserControl abc
Let's create a new instance of UserControl abc
Let's now assign the DataContext of abc to the ViewModel itself.
Let's return the newly created instance of UserControl abc
So what do we do to solve the problem in this question?
UserControl.xaml:
<UserControl ...
DataContextChanged="DaCoHasChanged">
<UserControl.Resources>
<FrameworkElement x:Key="ProxyElement" /> <!--Remove DataContext="{Binding}"-->
</UserControl.Resources>
<DataGrid ...>
<DataGridComboBoxColumn
ItemsSource="{Binding Path=DataContext.ProductCompanies,
Source={StaticSource ProxyElement}}"
... />
</DataGrid>
</UserControl>
UserControl.xaml.cs:
private void DaCoHasChanged(object sender, DependencyPropertyChangedEventArgs e)
{
var proxyElement = Resources["ProxyElement"] as FrameworkElement;
proxyElement.DataContext = e.NewValue; // instead of e.NewValue, you could
// also say this.DataContext
}
I am trying to figure out a way of getting rid of the code in the code-behind file. But till then, if someone else hits this problem, then they might be able to get inspired from this solution.
Credit to the concept behind this solution goes to: How to set the DataContext for a View created in DataTemplate from ViewModel
Try this
<DataGrid AutoGenerateColumns="False" ItemsSource="{Binding Products}">
<DataGridTemplateColumn Header="Company">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<ComboBox
ItemsSource="{Binding Path="{Binding DataContext.ProductCompanies,RelativeSource={RelativeSource AncestorType={x:Type DataGrid}}}"
DisplayMemberPath="Name" SelectedValuePath="Id"
SelectedValueBinding="{Binding CompanyId}" />
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
</DataGrid>
Now as I got your problem I think the problem is in DataGridComboBoxColumn I dont know why it not Binding using RelativeResource . Try it with DataGridTemplateColumn and you would not require any ProxyElement I hope this will help.

ComboBox validation error - why does it fail?

The idea is simple: to select a period in seconds from a RibbonComboBox and save that to an app instance property. The underlying data is in milliseconds but the combo displays them in seconds.
The property in my app stores the time in millis:
private int _updatePeriod;
public int UpdatePeriod
{
get { return _updatePeriod; }
set
{
_updatePeriod = value;
InvokePropertyChanged(new PropertyChangedEventArgs("UpdatePeriod"));
}
}
in XAML i define a list of possible choices, in millis like this:
<x:Array x:Key="UpdateFrequenciesCollection" Type="{x:Type System:Int32}" >
<System:Int32 >100</System:Int32>
<System:Int32 >200</System:Int32>
<System:Int32 >500</System:Int32>
<System:Int32 >1000</System:Int32>
<System:Int32 >2000</System:Int32>
</x:Array >
I then have a ribbon toolbar combo box setup like this:
<ribbon:RibbonComboBox IsEditable="False" AllowDrop="False" Grid.Column="1" Grid.Row="1" IsDropDownOpen="False">
<ribbon:RibbonGallery x:Name="RibbonGalleryUpdatePeriod" SelectedValue="{Binding Path=UpdatePeriod, Mode=TwoWay, Source={x:Static Application.Current}}">
<ribbon:RibbonGalleryCategory x:Name="RibbonGalleryCategoryUpdatePeriod" ItemTemplate="{StaticResource UpdatePeriodTemplate}" ItemsSource="{Binding Source={StaticResource UpdateFrequenciesCollection}}">
</ribbon:RibbonGalleryCategory>
</ribbon:RibbonGallery>
</ribbon:RibbonComboBox>
This is the simple template for the combo box item:
<DataTemplate x:Key="UpdatePeriodTemplate">
<StackPanel Orientation="Horizontal" >
<TextBlock Text="{Binding Converter={StaticResource DivideConverter}, ConverterParameter=1000, StringFormat={}{0:0.0 s}}"/>
</StackPanel>
</DataTemplate>
Functionally its okay, as in it sets and get the correct value in the combobox<->UpdatePeriod. However the dropdown always has a red border which I think indicates an a validation error. Any idea what can cause this, or how I can debug the error in some way?? Much appreciated!
If your Binding has associated validation rules but you do not specify an ErrorTemplate on the bound control, a default ErrorTemplate will be used to notify users when there is a validation error. The default ErrorTemplate is a control template that defines a red border in the adorner layer.
I guess RibbonComboBox does define a validation rule.
You can set the attached property PresentationTraceSources.TraceLevel and see the binding error but I would start with Snoop

Silverlight DataGrid set cell IsReadOnly programmatically

I am binding a data grid to a collection of Task objects. A particular column needs some special rules pertaining to editing:
<!--Percent Complete-->
<data:DataGridTextColumn Header="%"
ElementStyle="{StaticResource RightAlignStyle}"
Binding="{Binding PercentComplete, Mode=TwoWay, Converter={StaticResource PercentConverter}}" />
What I want to do is set the IsReadOnly property only for each task's percent complete cell based on a property on the actual Task object. I've tried this:
<!--Percent Complete-->
<data:DataGridTextColumn Header="%"
ElementStyle="{StaticResource RightAlignStyle}"
Binding="{Binding PercentComplete, Mode=TwoWay, Converter={StaticResource PercentConverter}}"
IsReadOnly={Binding IsNotLocalID} />
but apparently you can't bind to the IsReadOnly property on a data grid column. What is the best way do to do what I am trying to do?
I don't think that you can Bind directly to this. I have found this extended DataGrid for Silverlight which will do the trick though.
Extended DataGrid
It looks like the DataGridColumn.IsReadOnly Property is a DependencyProperty so it should be bindable. Change your XAML to IsReadOnly="{Binding IsNotLocalID}" (Note the added quotes) and see what happens. Are you getting any binding failures in the Visual Studio output window?

Silverlight 3: Using list of UserControls as ItemsSource for TreeView

I want to populate a TreeView with UserControls, but I only want the Name property to show up, not the entire UserControl. The following code gives me weird crashes as soon as I add something to myUCs:
C#:
var myUCs = new ObservableCollection<UserControl>();
MyTreeView.ItemsSource = myUCs;
XAML:
<controls:TreeView x:Name="MyTreeView">
<controls:TreeView.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Name}"/>
</DataTemplate>
</controls:TreeView.ItemTemplate>
</controls:TreeView>
Does anyone know how to use a list of UserControls as an ItemSource for TreeViews?
I found one not so convenient workaround: instead of a List of UserControls, use a Dictionary, and change the XAML to:
<controls:TreeView x:Name="MyTreeView">
<controls:TreeView.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Key.Name}"/>
</DataTemplate>
</controls:TreeView.ItemTemplate>
</controls:TreeView>
The same bug(?) exists in ListBox, a solution is provided here:
Use UIElements as ItemsSource of ListBox in Silverlight
That particular fix does not work for TreeView
You may have to create your own class that extends UserControl and override the ToString() method so that it returns the name property.

Resources