I have been searching for an answer to this question, but always find the reversed of what I am looking for.
like this Question: WPF Attached Property Data Binding with following answer:
IsChecked="{Binding Path=(local:Attached.Test), Mode=TwoWay, RelativeSource={RelativeSource Self}}"
But I would like to perform this kind of Binding in code-behind:
<Grid local:MyGridHelper.GridWidth = {Binding Elementname=XY, Path=ActualWidth} />
In code, setting a value for the attached Property is usually easy: MyGridHelper.SetGridWidth(mygrid, value). But how do I get a Binding into this instaed of just a value?
Related
My goal is to bind an element property in XAML to a property of the code behind class, while the DataContext still is a ViewModel.
The reason is, that I have some only UI-cosmetic properties in XAML which are not controlled by the ViewModel but by the code behind.
So essentially I search something like that:
<Element
Attribute = "{Binding ThatOneCodeBehind.WhateverProperty}"
OtherAttribute1 = "{Binding StillDataContextSomething}"
OtherAttribute2 = "{Binding StillDataContextSomething}"
/>
What is the correct binding Syntax for Attribute="{Binding ThatOneCodeBehind:WhateverProperty}"?
Your code behind is in some UIElement, let say Window. So give your element with code behind name and do bind to it. Of course property CodeBehindProperty should be defined there.
<Window x:Name="_this">
<TextBox Text="{Binding CodeBehindProperty, ElementName=_this}"/>
</Window>
Another way is to find the ancestor with defined type:
<TextBox Text="{Binding CodeBehindProperty, RelativeSource={RelativeSource AncestorType=Window}}"/>
I have a grid column defined. The parent grid gets its items from an ObservableCollection of type ItemClass. ItemClass has two properties: String Foo, and bool IsEditAllowed.
This column is bound to property Foo. There's a control template for editing the cell. I'd like to bind the ItemClass.IsEditAllowed property to the IsEnabled property of the TextBox in the template.
The question is how to bind it. Can this be done? The XAML below gets me "Cannot find source for binding with reference" in the debug trace.
The grid will let me bind the ItemClass itself to the field via some "custom" event thingy, and I can then bind to any of its properties. That's fine, but it seems kludgy. But if it's the only way, it's the only way.
<dxg:GridColumn
Header="Foo Column"
FieldName="Foo">
<dxg:GridColumn.EditTemplate>
<ControlTemplate>
<TextBox Text="{Binding Value, Mode=TwoWay}"
IsEnabled="{Binding Path=IsEditAllowed, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:ItemClass}, AncestorLevel=1}}" />
</ControlTemplate>
</dxg:GridColumn.EditTemplate>
</dxg:GridColumn>
There are two potentially easier ways to set up this binding.
Name the grid. Then your binding could look something like this (assuming dxg:GridControl has a property named "Items" and that you have assigned an instance of your ItemClass to that property):
<TextBox IsEnabled="{Binding Path=Items.IsEditAllowed, ElementName=MyGridControl} />
Use relative binding, but look for the GridControl rather than something nominally internal to the way GridControl works (that is, GridControlContentPresenter). This gets you away from the implementation details of GridControl, which are perhaps more likely to change in ways that break your application than are properties on GridControl itself.
<TextBox IsEnabled="{Binding Path=Items.IsEditAllowed, RelativeSource={RelativeSource AncestorType={x:Type dxg:GridControl}}}" />
You may also want to read up on the Visual Tree and the Logical Tree in WPF/xaml. The "Ancestor" in relative bindings refers to ancestors in the visual tree, that is, things like parent containers, and not to super- or base classes (as you've discovered, I think).
Here's the answer[1]. FindAncestor finds ancestors in the runtime XAML tree, not in arbitrary C# objects. It cannot walk up to the ItemClass instance from the member we're bound to. But we do know that somebody above us in the XAML tree bound us to that member, and he was bound to the ItemClass instance itself. So whoever that is, we find him, and then we've got the ItemClass.
So let's add debug tracing to the binding, and we'll see what the XAML situation looks like at runtime. No doubt there are other and probably smarter ways to do that, but I happen to know this one without any research.
First add this to the namespaces at the top of the XAML file:
xmlns:diag="clr-namespace:System.Diagnostics;assembly=WindowsBase"
...and then to the binding itself, add this:
diag:PresentationTraceSources.TraceLevel=High
Like so:
<TextBox Text="{Binding Value, Mode=TwoWay}"
IsEnabled="{Binding Path=IsEditAllowed, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:ItemClass}, AncestorLevel=1}, diag:PresentationTraceSources.TraceLevel=High}"
/>
At runtime, when the TextEdit's IsEnabled property tries to get a value from the binding, the binding walks up through the XAML tree looking for an ancestor of the specified type. It keeps looking until it finds one or runs out of tree, and if we put tracing on it, it traces the type of everything it finds the whole way up. We've told it to look for garbage that it'll never find, so it will give us a trace of the type of every ancestor back to the root of the tree, leaf first and root last. I get 75 lines of ancestors in this case.
I did that, and found a few likely candidates. I checked each one, and the winner turned out to be dgx:GridCellContentPresenter, which has a RowData property. RowData has a lot of properties, and RowData.Row is the row's instance of ItemClass. dxg:GridCellContentPresenter belongs to the DevExpress grid library we're using; in another vendor's grid class, there would presumably be some equivalent.
Here's the working binding:
<TextBox Text="{Binding Value, Mode=TwoWay}"
IsEnabled="{Binding Path=RowData.Row.IsEditAllowed, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type dxg:GridCellContentPresenter}, AncestorLevel=1}}"
/>
If DevExpress, the vendor, rewrites their GridControl class, we'll be in trouble. But that was true anyhow.
...
[1] Better answer, though it's too DevExpress specific to be of any real interest: The DataContext of the TextBox itself turns out to be dxg:EditGridCellData, which has a RowData property just like GridCellContentPresenter does. I can just use IsEnabled="{Binding Path=RowData.Row.IsEditAllowed}".
However, what I really wanted to do all along was not to present a grid full of stupid disabled textboxes, but rather to enable editing on certain rows in the grid. And the DevExpress grid lets you do that through the ShowingEditor event.
XAML:
<dxg:GridControl Name="grdItems">
<dxg:GridControl.View>
<dxg:TableView
NavigationStyle="Cell"
AllowEditing="True"
ShowingEditor="grdItems_TableView_ShowingEditor"
/>
</dxg:GridControl.View>
<!-- ... Much XAML ... -->
</dxg:GridControl Name="grdItems">
.cs:
private void grdItems_TableView_ShowingEditor(object sender, ShowingEditorEventArgs e)
{
e.Cancel = !(e.Row as ItemClass).IsEditAllowed;
}
I have a the following three items in my xaml that have there visibility changed depending on a boolean property located in the DataContext of the page.
<worklist:PhysicianQuickInsert Visibility="{Binding IsInsertingPhysician, Converter={StaticResource NullEmptyFalseToInvisibileConverter}}" />
<shared:LoadingIndicator Visibility="{Binding IsInsertingPhysician, Converter={StaticResource NullEmptyFalseToInvisibileConverter}}" />
<TextBlock Text="Test" Foreground="Red" Visibility="{Binding IsInsertingPhysician, Converter={StaticResource NullEmptyFalseToInvisibileConverter}}" />
The TextBlock and LoadingIndicator are becoming visible/collapsed, but the PhysicianQuickInsert fails to do anything.
I have verified that the getter of the boolean property is never retrieved for the PhysicianQuickInsert.
Why would this happen? PhysicianQuickInsert is a usercontrol, as is the LoadingingIndicator.
This can happen if you defined a new Visibility property on your UserControl. If you've done this, you may need to make sure that it's binding two-way by default, has a proper setter, etc.
Following the comments on my answer, this issue was that this user control binds to a another datacontext (different type) within it. I added a RelativeSource and it worked.
Thanks everyone!
I have a workable solution but I'm pretty convinced there's a better way of writing this.
I have a User Control with a Data Grid inside. The Data Grid's ItemsSource is set to {Binding Path=MyView} where MyView is an ICollectionView property of the View Model. The User Control's data context is set to the View Model.
In the data grid, I have a check box header. I want to bind the IsChecked state of the checkbox to a property in the View Model.
This is what I have so far and it seems to work, but I'm concerned this binding is unnecessarily complex. The UI is pretty basic so I would expect the binding to be more straightforward to write than it was.
Is there a better way to express such a binding?
<DataGridTemplateColumn.HeaderTemplate>
<DataTemplate>
<CheckBox IsChecked="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Views:MyUserControlClass}}, Path=DataContext.AllRowsSelected}" />
</DataTemplate>
</DataGridTemplateColumn.HeaderTemplate>
In such situations I use
ElementName=userControl
instead of
RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Views:MyUserControlClass}}
Also you can use
{Binding Parent.DataContext.AllRowsSelected, ElementName=LayoutRoot}
In this case I assume that LayoutRoot is the name of the element who's parent is the user control. Parent is its property. So binding is set to parent's DataContext property.
I prefer the last variant, because providing name for user control limits its usage.
EDIT
About LayoutRoot. This name is often provided for the top element in a Window or a UserControl, or just some layout:
<Window ...>
<Grid Name="LayoutRoot">
...
</Grid>
</Window>
There's nothing special about this name. Just often used. Same situation as with namespace aliases in xaml: sys (points to mscorlib), local (points to your application namespace), etc.
I'm trying to work with a datagrid using the MVVM pattern. The problem is that whenever I change the VM property which is binded to SelectedItem to null, the View doesn't "deselect" the currently selected item. This is my binding in xaml:
<DataGrid Grid.Column="0" Grid.Row="0"
ItemsSource="{Binding Path=Users}"
AutoGenerateColumns="False"
CanUserAddRows="False"
IsReadOnly="True"
SelectedItem="{Binding Path=SelectedUser, Mode=TwoWay}">
The SelectedItem binding works from the view to the VM thus in the SelectedUser property I always have the selected object. The problem is that in the VM I'm doing some stuff which sometimes changes the SelectedUser property to null so I would expect the datagrid to deselect the row as well. Instead, it remains selected and if I try to click on the same row, the property doesn't update. If I click on any other row, the property changes as expected.
Is there a way to make the datagrid deselect if it's binded property is set to null? Also I'm looking for a MVVM solution as I don't want to write code behind. I can solve this by writing code behind so don't waste time offering such solutions :)
l.e.: this is my property in the VM:
public RPLUser SelectedUser
{
get
{
return selectedUser;
}
set
{
selectedUser = value;
OnPropertyChanged("SelectedUser");
}
}
Thanks in advance!
I recommend to check the Output Window in visual studio and see if any Binding is failing.
Are you sure when you select something, the selection updates into the SelectedUser property?
Did u put a breakpoint in setter of SelectedUser and see that it is hitting when you select something on the datagrid?
The reasons for this Binding to break could be many ...
SelectedUser is of different type than individual Users.
SelectedUser does not match by reference with any items in Users.
How and where are you setting null?
The following code in my case works perfectly fine...
<tk:DataGrid MaxHeight="200" AutoGenerateColumns="False"
ItemsSource="{Binding}"
SelectedItem="{Binding MySelItem,
ElementName=MyDGSampleWindow,
Mode=TwoWay}"
IsReadOnly="True">
<tk:DataGrid.Columns>
<tk:DataGridTextColumn Header="Key"
Binding="{Binding Key,
Mode=OneWay}"/>
<tk:DataGridTextColumn Header="Value"
Binding="{Binding Value,
Mode=OneWay}"/>
</tk:DataGrid.Columns>
</tk:DataGrid>
When I set MyDGSampleWindow.MySelItem as null, the datagrid propertly deselects. Perhaps you might need to give us more input on how are you actually setting the value as null.
Did you try setting IsSynchronizedWithCurrentItem="True" in the xaml properties for the DataGrid? AFAIK, this will allow you to unselect it by setting the SelectedUser to null.
I cannot test it at the moment, but you could also try to add this in the setter of your property:
set
{
selectedUser = value;
OnPropertyChanged("SelectedUser");
ICollectionView collectionView = CollectionViewSource.GetDefaultView(Users);
collectionView.MoveCurrentTo(selectedUser);
}
(For ICollectionView to do anything, you will need to have IsSynchronizedWithCurrentItem set)
Like I said, I cannot test this right now. Also, the setter of the property is probably not the best place to put it. Maybe create an event handler for the PropertyChangedevent locally and put that logic there.
Let me know if it helps, else I'll see if I can run a short test...
Yeah may need to add the XAML UpdateSourceTrigger to update the UI.
SelectedItem="{Binding SomeProperty, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
The DataGrid will not Deselect it automatically as DataGridRow's IsSelected property should be set to False.
You can do that by Setting a style on DataGrid.. like
<Style x:Key="dataGridRowStyle"
BasedOn="{StaticResource {x:Type WPFToolkit:DataGridRow}}"
TargetType="{x:Type WPFToolkit:DataGridRow}">
<Setter Property="IsSelected" Value="{Binding Path=IsSelected}" />
</Style>
The IsSelected property should be the of the object i.e in your case RPLUser should have a property Isselected
Then before you set the SelectedUser to null... just do SelectedUser.IsSelected=False
And dont forget to attach this style to the DataGridRowStyle in Datagrid
I am using WPFToolkit you can modify the style if you are targetting .NET 4.0