I have a dynamically created DataGrid with the following XAML:
<DataGrid AutoGenerateColumns="False" ItemsSource="{Binding Cells}" IsReadOnly="True" Visibility="{Binding VisibilityStatus}"
CanUserResizeColumns="False" CanUserAddRows="False" RowBackground="{Binding Color}" Margin="0" RowHeight="25" HeadersVisibility="All">
<DataGrid.Columns>
<DataGridTemplateColumn Header="{Binding Path=Header}">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<ComboBox ItemsSource="{Binding UniqueValuesToLapMapping.Keys, UpdateSourceTrigger=PropertyChanged}"
SelectedItem="{Binding SelectedValue, Mode=TwoWay}"
SelectedIndex="0" Width="55"
Background="{Binding Color}"/>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
</DataGrid>
The table is being displayed correctly with all of its content except for the header. No matter in which class I put the Header Property I can't seem to bind to it. I've tried putting it into the ViewModel, putting it into the class that has the "Cells" property, and putting it into the class that has all the Properties that I bind to in the ComboBox.
I have also tried binding without the "Path=" and setting the UpdateSourceTrigger to OnPropertyChanged and changing the properties value on the runtime: nothing seems to work. What am I missing here?
This is the Property I am binding to that I've put in all these classes:
private string _header = "TEST";
public string Header
{
get { return _header; }
set { SetField(ref _header, value, "Header"); }
}
It turns out that the was not in any logical or visual tree. This solution worked for me:
http://www.thomaslevesque.com/2011/03/21/wpf-how-to-bind-to-data-when-the-datacontext-is-not-inherited/
Related
Consider this XAML:
<UserControl>
<TextBlock Text="{Binding NestedObject.Name}" TextWrapping="Wrap"/>
<DataGrid DataContext="{Binding}" AutoGenerateColumns="False">
<DataGrid.Columns>
<DataGridTextColumn Header="Name" Binding="{Binding NestedObject.Name}" />
</DataGrid.Columns>
</DataGrid>
</UserControl>
In the case of the TextBlock the object property would be correctly displayed, but when using it in a DataGrid, nothing is displayed.
This is odd since I call it the same way in both cases and I thought that when no data context was specified it was falling back to the parent data context.
Am I missing something in the declaration ?
Note
I'm using the UserControl in the MainWindow and an object with a NestedObject property is assigned to its data context. Also, Name is implementing INotifyPropertyChanged.
You generally bind a column in a DataGrid to a property of an item in the DataGrid's ItemsSource, i.e. your current binding will only work if the ItemsSource property is bound or set to an IEnumerable<T> and the type T has a SomeObject property.
If you want to to bind to a property of another object, you could use a {RelativeSource} to explictly specify the source of the binding:
<DataGrid ItemsSource="{Binding}" AutoGenerateColumns="False">
<DataGrid.Columns>
<DataGridTemplateColumn Header="Name">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBlock Text="{Binding DataContext.SomeObject.Name, RelativeSource={RelativeSource AncestorType=UserControl}}" />
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
<DataGridTemplateColumn.CellEditingTemplate>
<DataTemplate>
<TextBox Text="{Binding DataContext.SomeObject.Name, RelativeSource={RelativeSource AncestorType=UserControl}}" />
</DataTemplate>
</DataGridTemplateColumn.CellEditingTemplate>
</DataGridTemplateColumn>
<DataGridTextColumn Header="Name" Binding="{Binding SomeObject.Name}" />
</DataGrid.Columns>
</DataGrid>
I have a database controller class that creates a couple of table adapters and a dataset. Also, I've created a model for each table row and I create an ObservableCollection<MembersModel> with model instances for each row.
private ObservableCollection<DoubleTeamModel> _doubleTeams = new ObservableCollection<DoubleTeamModel>();
foreach (UsersDataSet.Double_TeamsRow row in ds.Double_Teams)
{
_doubleTeams.Add(new DoubleTeamModel(row));
}
In my viewmodel I have a function that returns a BindingList
public BindingList<TeamMatchModel> TeamQualificationMatches
{
get
{
return new BindingList<TeamMatchModel>(App.Data.GetDoubleTeamMatches().Where(
match => match.TournamentId == CurrentTournament.Id &&
match.Status == MatchStatus.QualificationsPlaying).ToList<TeamMatchModel>());
}
}
This BindingList is then bound to a datagrid
<DataGrid x:Name="dgrTeamQualificationMatches" AutoGenerateColumns="False" Margin="5,10,5,10"
ItemsSource="{Binding TeamQualificationMatches, Mode=TwoWay}"
CanUserAddRows="False" CanUserDeleteRows="False" CanUserReorderColumns="False" CanUserSortColumns="False"
SelectionMode="Single" SelectionUnit="FullRow"
RowStyle="{DynamicResource EditableRows}" CellStyle="{DynamicResource EditableTableCells}">
<DataGrid.Columns>
<DataGridTextColumn Header="Team A" Binding="{Binding TeamANames, Mode=OneWay}"
CellStyle="{DynamicResource ScoreATableCells}" Width="Auto" />
<DataGridTemplateColumn Header="Highest shot 1" Width="Auto">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding TeamANameA}" />
<TextBox Text="{Binding HighestShotTeamAPlayerA, Mode=TwoWay, UpdateSourceTrigger=LostFocus}" />
</StackPanel>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
// Some other columns
</DataGrid.Columns>
</DataGrid>
This causes a InvalidOperationException with this message:
A TwoWay or OneWayToSource binding cannot work on the read-only property 'TeamQualificationMatches' of type 'CompetitionAgent.ViewModel.TournamentViewModel'
The grid should be writeable (Mode=TwoWay), but each column that's supposed to be read-only has it's mode set to OneWay. When did the entire collection become read-only according to the exception?
I'm trying to learn MVVM (Using MVVMLight Toolkit). But i'm stuck.
In ViewModel have an ObservableCollection
private ObservableCollection<Phone> _phoneNumbers;
public ObservableCollection<Phone> PhoneNumbers
{
get { return _phoneNumbers; }
private set
{
_phoneNumbers = value;
}
}
In ViewModel constructor fill it in such way PhoneNumbers = new ObservableCollection<Phone>(Guest.Person.Phones);
In view have
<DataGrid x:Name="PhoneNumbersDataGrid" HorizontalAlignment="Left" Grid.Column="3" Grid.Row="11" VerticalAlignment="Top" Height="86" Width="auto" ItemsSource="{Binding PhoneNumbers, Mode=OneWay}" AutoGenerateColumns="False" IsReadOnly="True">
<DataGrid.Columns>
<DataGridTextColumn Binding="{Binding PhoneNumber}" Header="Phone Number" IsReadOnly="True"/>
<DataGridTemplateColumn Header="Delete">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<Button Command="{Binding Guest.DeletePhoneCommand, Mode=OneWay, Source={StaticResource Locator}}">Delete</Button>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
</DataGrid>
In DeletePhoneCommand I'm trying to change PhoneNumbers e.g.
PhoneNumbers = new ObservableCollection<Phone>();
RaisePropertyChanged("PhoneNumbers");
Collection became empty but datagrid displays filled collection without any changes. "get" of Collection fires only when view is loading. Even when i RaisePropertyChanged("PhoneNumbers") it doesn't fire.
What am I doing wrong?
If you want to notify the changes to your collection you should use Mode = TwoWay
Two way will enable any change in object is reflected to the UI and any change in UI is reflected in the object.
<DataGrid x:Name="PhoneNumbersDataGrid" HorizontalAlignment="Left" Grid.Column="3" Grid.Row="11" VerticalAlignment="Top" Height="86" Width="auto" ItemsSource="{Binding PhoneNumbers, Mode=TwoWay}" AutoGenerateColumns="False" IsReadOnly="True">
<DataGrid.Columns>
<DataGridTextColumn Binding="{Binding PhoneNumber,Mode=TwoWay}" Header="Phone Number" IsReadOnly="True"/>
<DataGridTemplateColumn Header="Delete">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<Button Command="{Binding Guest.DeletePhoneCommand, Mode=OneWay, Source={StaticResource Locator}}">Delete</Button>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
</DataGrid>
If I'm not mistaken, the binding is broken when you reinitialize the collection. to fix this you will have to work on the already initialized collection. e.g :
clear the list using the
PhoneNumbers.Clear()
to add to the list, using
PhoneNumbers.Add(item)
you don't need to raise the property changed event to update the list in the UI when you're using ObservableCollection. You will need it if you change a property of the item on the list.
at first clear the list with
Clear()
then add item
PhoneNumbers.Add(item)
I’ve got a datagrid with one column displaying a combobox. At present the new row is shown underneath existing rows – as expected.
<grid>
<DockPanel Grid.Column="0" Grid.Row="0">
<TextBlock DockPanel.Dock="Top" Text="Role Groups"/>
<DataGrid DockPanel.Dock="Bottom" Name="dgRoleGroups" AutoGenerateColumns="False" CanUserAddRows="True" CanUserDeleteRows="True" HorizontalAlignment="Left" ItemsSource="{Binding ListSecurityUserRoleGroup}">
<DataGrid.Columns>
<DataGridTemplateColumn Header="Role Group" Width="*">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<ComboBox ItemsSource="{Binding ListSecurityRoleGroup,
RelativeSource={RelativeSource AncestorType=UserControl}}"
DisplayMemberPath="Description" SelectedValuePath="ID"
SelectedValue="{Binding RoleGroupID}”/>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
</DataGrid>
</DockPanel>
</Grid>
Where ListSecurityUserRoleGroup is an ObservableCollection of:
public class tbl_SecurityUserRoleGroup_Row
{
public int UserID { get; set; }
public int RoleGroupID { get; set; }
}
And ListSecurityRoleGroup is a list of:
public class tbl_Security_RoleGroup_Row
{
public int ID { get; set; }
public string PublicID { get; set; }
public string Description { get; set; }
}
In the code behind I’ve got:
dgRoleGroups.DataContext = ListSecurityUserRoleGroup;
dgRoleGroups.ItemsSource = ListSecurityUserRoleGroup;
The pic below shows that the binding for the first row is working; and I’ve got a new row and can pick a value for that.
However, I then cannot get another new row. This is the problem I’m trying to solve.
From reading other posts, I suspect I’m missing something in the realm of IEditableObject, INotifiyProperyChanged or because there is only one column in this datagrid, maybe need to trigger something from the combobox SelectedChanged – like check to see if a blank row is visible and if not, create one ?
I've not found a post matching my issue but i'm sure it's there...
There may be other solutions that do not involve datagrids however once I’ve got this working, my next task is a datagrid containing 2 columns of comboboxes, which will need to work there.
You just need to add an edit template :
<Grid>
<DockPanel Grid.Column="0" Grid.Row="0">
<TextBlock DockPanel.Dock="Top" Text="Role Groups"/>
<DataGrid DockPanel.Dock="Bottom" Name="dgRoleGroups" AutoGenerateColumns="False"
CanUserAddRows="True" CanUserDeleteRows="True"
HorizontalAlignment="Left" ItemsSource="{Binding ListSecurityUserRoleGroup}">
<DataGrid.Columns>
<DataGridTemplateColumn Header="Role Group" Width="*">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<ComboBox ItemsSource="{Binding ListSecurityRoleGroup,
RelativeSource={RelativeSource AncestorType=UserControl}}" SelectedValue="{Binding RoleGroupID,UpdateSourceTrigger=PropertyChanged}"
DisplayMemberPath="Description" SelectedValuePath="ID" IsHitTestVisible="False">
</ComboBox>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
<DataGridTemplateColumn.CellEditingTemplate>
<DataTemplate>
<ComboBox ItemsSource="{Binding ListSecurityRoleGroup,
RelativeSource={RelativeSource AncestorType=UserControl}}"
DisplayMemberPath="Description" SelectedValuePath="ID"
SelectedValue="{Binding RoleGroupID,UpdateSourceTrigger=PropertyChanged}"
/>
</DataTemplate>
</DataGridTemplateColumn.CellEditingTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
</DataGrid>
</DockPanel>
</Grid>
You can also modify the combobox template to look like a textblock :
<ComboBox.Template>
<ControlTemplate>
<TextBlock Text="{Binding SelectedItem.Description,RelativeSource={RelativeSource Mode=TemplatedParent}}"></TextBlock>
</ControlTemplate>
</ComboBox.Template>
I have a problem whit binding in wpf i have a textbox where i can do some input, then i try to bind the textinput to a custom usercontrol. This work for the usercontrol within RowDetailsTemplate but not in the CellTemplate. For each object in the CellTemplate i get this error output:
System.Windows.Data Error: 4 : Cannot find source for binding with reference 'ElementName=ScaleTextBox'. BindingExpression:Path=Text; DataItem=null; target element is 'Chart' (Name=''); target property is 'MaxValue' (type 'Int32')
My code looks like this:
XAML
<ToolBarTray ToolBarTray.IsLocked="True" DockPanel.Dock="Top" Height="25">
<ToolBar Name="ButtonBar" >
<TextBox Height="23" Name="ScaleTextBox" Width="120" Text="400"/>
</ToolBar>
</ToolBarTray>
<DataGrid ItemsSource="{Binding Path=Items}" AutoGenerateColumns="False" IsReadOnly="True" RowHeight="25" RowDetailsVisibilityMode="VisibleWhenSelected">
<DataGrid.RowDetailsTemplate>
<DataTemplate>
<StackPanel Orientation="Vertical" >
<my:UserControl ItemsSource="{Binding Path=Samples}" MaxValue="{Binding ElementName=ScaleTextBox, Path=Text}"/>-->
</StackPanel>
</DataTemplate>
</DataGrid.RowDetailsTemplate>
<DataGrid.Columns>
<DataGridTemplateColumn MinWidth="150" Header="Chart" >
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<my:UserControl ItemsSource="{Binding Path=Samples}" MaxValue="{Binding ElementName=ScaleTextBox, Path=Text}"/><!-- this is the problem -->
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
</DataGrid>
C#
public static readonly DependencyProperty MaxValueProperty = DependencyProperty.Register("MaxValue", typeof(int), typeof(Chart), new FrameworkPropertyMetadata(MaxValuePropertyChanged));
private static void MaxValuePropertyChanged(DependencyObject source, DependencyPropertyChangedEventArgs e)
{
Console.WriteLine(e.NewValue);
}
What do i do wrong?
You can try this:
<DataTemplate>
<my:UserControl
ItemsSource="{Binding Path=Samples}"
MaxValue="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type local:YourControlClassName}}, ElementName=ScaleTextBox, Path=Text}"/>
</DataTemplate>
Ok, i Solved it using ElementSpy, to see how elementSpy works look here: http://joshsmithonwpf.wordpress.com/2008/07/22/enable-elementname-bindings-with-elementspy/
and the xaml:
<my:UserControl local:ElementSpy.NameScopeSource="{StaticResource ElementSpy}" ItemsSource="{Binding Path=Samples}" MaxValue="{Binding ElementName=ScaleTextBox, Path=Text}" />
From this link
The Columns collection is just a property in the Datagrid; this collection is not in the logical (or visual) tree, therefore the DataContext is not being inherited, which leads to there being nothing to bind to.
Hence it works for your RowDetailsTemplate and not for your columns I guess.