How can I update the source of a bound custom dependency property? - wpf

I have a custom DepenencyProperty which determines a UserControl's visibility. It is usually bound to a boolean value, however I would like to set it False when the Escape key is hit.
The problem is, I don't want to overwrite the binding, I want to update the bindings source value. How can I do this in code behind?
For example, with this XAML
<local:MyControl IsVisibile="{Binding IsControlVisible}" />
I want to update the value of IsControlVisible to false, not MyControl.IsVisible

This should be possible via BindingExpressions, try something like this:
private void MyControl_KeyDown(object sender, KeyEventArgs e)
{
if (e.Key == Key.Escape)
{
var source = sender as FrameworkElement;
var expression = source.GetBindingExpression(UIElement.IsVisibleProperty);
(expression.DataItem as MyDataItem).IsControlVisible = false;
}
}
(If you do not reuse the UIElement.IsVisibleProperty you need to specify it via MyControl.IsVisibleProperty of course)
Here is a reflection-using method:
var source = sender as FrameworkElement;
var expression = source.GetBindingExpression(UIElement.IsVisibleProperty);
var dataType = expression.DataItem.GetType();
dataType.GetProperties().Single(x => x.Name == expression.ParentBinding.Path.Path)
.SetValue(expression.DataItem, false, null);

WPF 4? Use SetCurrentValue:
this.SetCurrentValue(IsControlVisibleProperty, false);
This won't overwrite the binding, but will instead push false to the binding source.

Related

wpf datagrid filter column header value access

I am using control datagrid filter control from http://www.codeproject.com/Articles/42227/Automatic-WPF-Toolkit-DataGrid-Filtering and it is working fine.
I want to save filter conditions ( header textboxes value ).
How can I get header/textboxes value on any button click and again set some header textboxes in some other event.
[More details]
I am consuming Filter control in one of my wpf application.Downloaded project is also containing a consumer test project (DataGridFilterTest). add a simple button with click event outside the grid ( no relation with grid).NOw I filter the data with some text in header column textbox. The added button click event i want the value or object of this textbox.Idea is I will save these text somewhere in xml and later on next time ( new request),i ll open grid with pre fixed data filter with same text.
Thanks
What a about a method like this one, where you go through the VisualTree to get the desired values. Once you have the DataGridColumnHeader and DataGridColumnCell is easy to get the value:
private void DataGrid_MouseRightButtonUp(object sender,
MouseButtonEventArgs e)
{
DependencyObject dep = (DependencyObject)e.OriginalSource;
// iteratively traverse the visual tree
while ((dep != null) &&
!(dep is DataGridCell) &&
!(dep is DataGridColumnHeader))
{
dep = VisualTreeHelper.GetParent(dep);
}
if (dep == null)
return;
if (dep is DataGridColumnHeader)
{
DataGridColumnHeader columnHeader = dep as DataGridColumnHeader;
// do something
}
if (dep is DataGridCell)
{
DataGridCell cell = dep as DataGridCell;
// do something
}
}
I took it from here http://www.scottlogic.com/blog/2008/12/02/wpf-datagrid-detecting-clicked-cell-and-row.html, it can be very useful for you
I faced the exact same problem. Here is a solution. It might not be the most elegant one, but it works.
In the Datagrid's Loaded event, find the TextBox elements. You can use the algorithm written here. You can narrow down the elements by checking their names, which will be "PART_TextBoxFilter", and their type will be DataGridFilterLibrary.Support.DelayTextBox (which descends from TextBox). It will still find more than you need, you can check if their binding paths are not empty (that is not part of the below sample). The point is to hook a KeyDown event handler on them:
void dgFilter_Loaded(object sender, RoutedEventArgs e)
{
foreach (TextBox tb in FindVisualChildren<TextBox>(sender as DataGrid))
{
if (tb != null && tb.Name == "PART_TextBoxFilter")
{
tb.KeyUp += new KeyEventHandler(tbDelayTextBox_KeyUp);
}
}
}
In the event handler method, the filter values can be reached, but that is not enough. The name of the binding path must be saved as well, otherwise you will not know which column it should filter. Furthermore, you can have multiple datagrids in your window, so the name of the datagrid is also important.
I used a Dictionary for saving the filters.
Dictionary<string,string> filterValues;
void tbDelayTextBox_KeyUp(object sender, KeyEventArgs e)
{
string dgName;
var bindingPath = DelayTextBoxBindingPath(sender as DataGridFilterLibrary.Support.DelayTextBox, out dgName);
if (!String.IsNullOrEmpty(bindingPath))
{
var key = dgName + "_" + bindingPath;
if (filterValues.ContainsKey(key))
{
filterValues[key] = ((TextBox)sender).Text;
}
else
{
filterValues.Add(key, ((TextBox)sender).Text);
}
}
}
Here is the method to get the binding path and the datagrid's name:
string DelayTextBoxBindingPath(DataGridFilterLibrary.Support.DelayTextBox __dtb, out string datagridName)
{
datagridName = String.Empty;
var result = String.Empty;
if (__dtb != null)
{
BindingExpression be = __dtb.GetBindingExpression(TextBox.TextProperty) as BindingExpression;
if (be != null)
{
var dgcf = be.DataItem as DataGridFilterLibrary.DataGridColumnFilter;
if (dgcf != null && dgcf.FilterCurrentData != null)
{
result = dgcf.FilterCurrentData.ValuePropertyBindingPath;
datagridName = dgcf.DataGrid.Name;
}
}
}
return result;
}
To save/load the filter, you can use the window's OnClosing, and the Datagrid's Loaded event (above), respectively.
Hope this helps.

How to update filter on wpf datagrid?

i want to filter wpf datagrid, and i do that in this way,i use datagridcolumnsheader and put a textbox in headers and use them filter each column:
private void TextBox_TextChanged(object sender, TextChangedEventArgs e)
{
textSearch = (sender as TextBox).Text;
CollectionViewSource.Filter += new FilterEventHandler(FilterEvent);
}
and
private void FilterEvent(object sender, FilterEventArgs e)
{
if (propertyName == null)
return;
var a = e.Item.GetType().GetProperty("Name");
if (a != null)
{
if (textSearch != "")
{
var s = a.GetValue(e.Item, null);
if (s != null)
e.Accepted = s.ToString().Contains(textSearch);
else
e.Accepted = false;
}
else
e.Accepted = true;
}
}
it works fine for a column like id,but when i want to make filter on another column like name ,it filters the list just by name and dosen't keep the past filter,for example if i filter the list by id=2 ,and then filter it by name='a' ,it just filters list by name='a'!
To apply multiple filters to a collection bound to a WPF DataGrid you should instantiate a CollectionViewSource object as a proxy between the view and the collection (this will also work with other collection controls). Doing so will allow you to subscribe multiple filter event handlers to it's Filter event. Filters are applied in the order in which they are subscribed and can be removed by unsubscribing them.
If you used the CollectionViewSource.GetDefaultView() static method in your codebehind or ViewModel, this will return an instance of an ICollectionView which will only support a single filter with a Filter property.
Your can find an example with source code here http://www.codeproject.com/Articles/442498/Multi-filtered-WPF-DataGrid-with-MVVM

WPF Datagrid -- programmatic selection of row seems to break multi-select (shift-click multiselect, specifically)

I have a WPF DataGrid control with a SelectionUnit of "FullRow" and SelectionMode of "Extended" that I'm programmatically selecting an item in (the first item, usually). The selection works, but for some reason any form of programmatic selection seems to break the shift-select multiselect ability.
If I single click another item in the DataGrid (so the item I just clicked is the only item selected), then shift-select will work. It only seems to break if I've programmatically selected the item. Additionally, control-click works to select multiple items in either case -- it seems to only be shift-select that is broken.
I've tried various forms of programmatically selecting the single item, from as simple as myGrid.SelectedIndex = 0, to using the DataGrid's ItemContainerGenerator to get an instance of the DataGridRow object and setting IsSelected = true on it, but to no avail.
To re-iterate -- programmatic selection of an item works, but it breaks shift-click selection.
Has anyone run into this before? I've tried setting focus on the DataGridRow instance that is programmatically selected, but it doesn't seem to help?
I succeeded to work around this problem using reflection:
var method = typeof(DataGrid).GetMethod("HandleSelectionForCellInput", BindingFlags.Instance | BindingFlags.NonPublic);
method.Invoke(MyDataGrid, new object[] { cellToSelect, false, false, false });
I struggled with this problem for multiple days and tried a lot of things that I found on the internet. In the end, I found the solution that works for me by studying the source code of the DataGrid.
In the DataGrid I noticed a member variable called _selectionAnchor and guessed that this must be the starting point for when a user expands the selection in the grid. My solution is to set this member to the first cell of the row that is selected. If a row is selected in code, than this fix makes sure that when expanding the selection it starts at the selected row.
Please note that I used the code from this issue to enable multiselect. Then, in file MainWindow.xaml.cs, I added this code:
private void ExampleDataGrid_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
if (ExampleDataGrid.SelectedItems.Count > 0)
{
ExampleDataGrid.ScrollIntoView(ExampleDataGrid.SelectedItems[0]);
// Make sure that when the user starts to make an extended selection, it starts at this one
foreach (var cellInfo in ExampleDataGrid.SelectedCells)
{
if (cellInfo.Column.DisplayIndex == 0)
{
var cell = GetDataGridCell(cellInfo);
cell?.Focus();
var field = typeof(DataGrid).GetField("_selectionAnchor", BindingFlags.NonPublic | BindingFlags.Instance);
field?.SetValue(ExampleDataGrid, cellInfo);
break;
}
}
}
}
public DataGridCell GetDataGridCell(DataGridCellInfo cellInfo)
{
var cellContent = cellInfo.Column.GetCellContent(cellInfo.Item);
if (cellContent != null)
{
return (DataGridCell)cellContent.Parent;
}
return null;
}
In the xaml file:
<vm:CustomDataGrid x:Name="ExampleDataGrid" ItemsSource="{Binding ImportItems}"
SelectedItemsList="{Binding SelectedImportItems, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
AutoGenerateColumns="False" SelectionMode="Extended" IsReadOnly="True" CanUserAddRows="False"
SelectionChanged="ExampleDataGrid_SelectionChanged">
Remember there is a difference between focus and keyboard focus. When you select the item in code, check to see what control has Keyboard focus / regular focus. I'm guessing that the data grid loses this focus until you click on it with the mouse and then it regains the focus needed to use the ctrl function.
I ran into this issue in a WPF user control we were hosting inside a C++ application.
I just resolved exactly the same problem with the help of #ezolotko's snippet.
Because the grid is dynamically generating rows I needed to subscribe to ItemContainerGenerator.StatusChanged event and find the first cell in a row representing this element.
To find the cell I used DataGridHelper class and wrapped it all in an attached behaviour:
using System.Reflection;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Controls.Primitives;
using Speedwell.WPF.Helpers;
namespace Speedwell.WPF.Behaviors
{
public static class DataGridSingleRowSelected
{
public static readonly DependencyProperty IsSelectionFixEnabledProperty = DependencyProperty.RegisterAttached
(
"IsSelectionFixEnabled",
typeof(bool?),
typeof(DataGridSingleRowSelected),
new PropertyMetadata(null, IsSelectionFixEnabledChanged)
);
public static bool GetIsSelectionFixEnabled(DataGrid element)
{
return (bool)element.GetValue(IsSelectionFixEnabledProperty);
}
public static void SetIsSelectionFixEnabled(DataGrid element, bool value)
{
element.SetValue(IsSelectionFixEnabledProperty, value);
}
private static void IsSelectionFixEnabledChanged(DependencyObject sender, DependencyPropertyChangedEventArgs args)
{
var dataGrid = sender as DataGrid;
if(dataGrid != null)
{
if(args.OldValue == null)
{
dataGrid.ItemContainerGenerator.StatusChanged += (s, e) => ContainerStatusChanged(dataGrid, ((ItemContainerGenerator)s));
}
}
}
private static void ContainerStatusChanged(DataGrid dataGrid, ItemContainerGenerator generator)
{
if(generator != null && generator.Status == GeneratorStatus.ContainersGenerated && dataGrid.SelectedItems.Count == 1)
{
var row = (DataGridRow)dataGrid.ItemContainerGenerator.ContainerFromItem(dataGrid.SelectedItems[0]);
if(row != null)
{
var cell = dataGrid.GetCell(row, 0);
if(cell != null)
{
SelectCellMethod.Invoke(dataGrid, new object[] { cell, false, false, false });
}
}
}
}
private static readonly MethodInfo SelectCellMethod = typeof(DataGrid).GetMethod("HandleSelectionForCellInput", BindingFlags.Instance | BindingFlags.NonPublic);
}
}
As you can see the proper selection is only applied when there is a single (1) row selected and this is exactly what I need and it seems it also what #Jordan0Day requested.

Attached properties order

What is the order in which attached properties are applied to an object ? I guess I should ignore this, but here my scenario:
I've got an attached property to stick the VM to the View, and then, another attached property that depend on the first one. I'm trying to see what happen if the second is set up before the first, but I can't manage to get the error! ie the first ( the model ) is always set up before the second, whatever is the order in xaml. Who is driving the order of assigment? Can I change it?
Now I'm dealing with the late assigmement by subscribing the proeprty change event:
DependencyPropertyDescriptor dd = DependencyPropertyDescriptor.FromProperty(FrameworkElement.DataContextProperty,depo.GetType());
dd.AddValueChanged(depo, (s, a) =>
{
ChangeDatacontext(s as DependencyObject);
}
and for simulate the problem I setup manually a new datacontext to the object.
Thanks,
Felix
I can't directly answer this question, because I never rely on which property is set before the other, but you can manage things with a method that both attached properties use.
here is an example from my current code:
public static readonly DependencyProperty RuleVMProperty =
DependencyProperty.RegisterAttached("RuleVM", typeof(DocumentRuleViewModel), typeof(DocumentRuleViewModel), new UIPropertyMetadata(null, RuleVMChanged));
public static void RuleVMChanged(DependencyObject sender, DependencyPropertyChangedEventArgs args)
{
var el = GetRefid(sender);
var vm = args.NewValue as DocumentRuleViewModel;
if(vm==null)
return;
vm.SetDocumentFromRefid(sender, el);
}
public static readonly DependencyProperty RefidProperty =
DependencyProperty.RegisterAttached("Refid", typeof(XmlElement), typeof(DocumentRuleViewModel), new UIPropertyMetadata(RefidChanged));
public static void RefidChanged(DependencyObject sender, DependencyPropertyChangedEventArgs args)
{
var el = args.NewValue as XmlElement;
var vm = GetRuleVM(sender);
if (vm == null)
return;
vm.SetDocumentFromRefid(sender, el);
}
private void SetDocumentFromRefid(DependencyObject sender, XmlElement element)
{
... // this is where the actual logic sits
}
so essentially you have two changed handlers and whichever triggers last executes the logic because it sees if the other property is null.

WPF: How to get name or other unique identifier of calling object in event handler for serialization?

I have a GridView and want to serialize column widths across sessions. My idea of how to accomplish this is to attach a behavior to the GridViewColumns in such a way that each time the width of a column is changed the attached event handler is called and stores the new width. This already works well.
The only remaining problem:
How do I know in the event handler which GridViewColumn sent the event? I obviously need to know that in order to be able to store the width and later set the width on the correct column when restoring. Ideally I would like to use the name specified in XAML as column identifier.
Here is my code. XAML:
<GridView>
<GridViewColumn x:Name="GridColumn0"
HeaderTemplate="{StaticResource GridViewHeaderTemplate}" HeaderContainerStyle="{StaticResource GridViewHeaderStyle}"
Header="{x:Static strings:Strings.MainWindow_AppLog_Header_Severity}"
behaviors:GridViewBehaviors.PersistColumnWidth="True">
C# (please scroll down - question at bottom):
// Register the property used in XAML
public static readonly DependencyProperty PersistColumnWidthProperty =
DependencyProperty.RegisterAttached("PersistColumnWidth", typeof(bool), typeof(GridViewBehaviors),
new FrameworkPropertyMetadata(false, new PropertyChangedCallback(OnPersistColumnWidthChanged)));
// Provide read access to the value
public static bool GetPersistColumnWidth(DependencyObject d)
{
return (bool)d.GetValue(PersistColumnWidthProperty);
}
// Provide write access to the value (set from XAML)
public static void SetPersistColumnWidth(DependencyObject d, bool value)
{
d.SetValue(PersistColumnWidthProperty, value);
}
// This gets called once when the XAML is compiled to BAML
// Set the event handler
private static void OnPersistColumnWidthChanged(DependencyObject sender, DependencyPropertyChangedEventArgs args)
{
GridViewColumn column = sender as GridViewColumn;
if (column == null)
return;
// Couple the UI event with a delegate
if ((bool)args.NewValue)
((INotifyPropertyChanged)column).PropertyChanged += new PropertyChangedEventHandler(PersistWidth);
else
((INotifyPropertyChanged)column).PropertyChanged -= new PropertyChangedEventHandler(PersistWidth);
}
// Deal with the events
static void PersistWidth(object sender, PropertyChangedEventArgs e)
{
GridViewColumn column = sender as GridViewColumn;
if (column == null)
return;
// We are only interested in changes of the "ActualWidth" property
if (e.PropertyName != "ActualWidth")
return;
// Ignore NaNs
if (column.ActualWidth == double.NaN)
return;
// Persist the width here
// PROBLEM:
// How to get a unique identifier for column, ideally its name set in XAML?
}
I don't think you will be able to access the x:Name, but you should be able to define your own column type that has a Name property that you can set.
So instead of adding GridViewColumns you add NamedColumn objects in your xaml.
Define a type that derives from GridViewColumn:
public class NamedColumn : GridViewColumn
{
public string ColumnName {get; set;}
}
And use it in your xaml:
<GridView>
<NamedColumn ColumnName="GridColumn0" .... blablalba more stuff here />
...
NoW you should be able to cast the sender to a NamedColumn and access its name property.
can you use column.Header.ToString()? see here for why name from XAML isn't possible: How to get x:Name value runtime
Thanks for your answers, Robert, Thomas and Rune. I like Runes answer, but I found something even easier for my situation here:
I changed the type of the attached property from bool to string and simply store the name of the column there. The relevant changes are below.
XAML:
<GridViewColumn
behaviors:GridViewBehaviors.PersistColumnWidth="MainWindow_AppLog_Column0">
C#:
public static readonly DependencyProperty PersistColumnWidthProperty =
DependencyProperty.RegisterAttached("PersistColumnWidth", typeof(string),
typeof(GridViewBehaviors), new FrameworkPropertyMetadata(string.Empty,
new PropertyChangedCallback(OnPersistColumnWidthChanged)));
static void PersistWidth(object sender, PropertyChangedEventArgs e)
{
// This now yields "MainWindow_AppLog_Column0"
string columnID = GetPersistColumnWidth(column);
I'm not sure it would work, but you can try something like that:
INameScope nameScope = NameScope.GetNameScope(column);
string name = nameScope
.Where(kvp => kvp.Value == column)
.Select(kvp => kvp.Key.ToString())
.FirstOrDefault();

Resources