XAML Data Binding Enabled from Function | Lock View Conditionally - wpf

Richt now I am checking in the code behind whether or not the controls in the view should be enabled:
public void Lock() {
if (_status != 40 && _status != 60)
{
txt1.isEnabled = false;
txt2.isEnabled = false;
txt3.isEnabled = false;
}
}
However, for pages with lot's of controls, this is a hassle. Is it possible to achieve the same with Data Binding? Something like this:
public void isEnabled() {
if (_status != 40 && _status != 60)
return false;
else
return true;
}
And then bind the controls to this function? (Sometimes the conditions for enabling/disabling a view are not as simple as in the example. It should be possible to do quite complex conditions.)
This program is developed with Xamarin. It should not make a difference for WPF.

create a property
public bool isEnabled
{
get
{
if (_status != 40 && _status != 60)
return false;
else
return true;
}
}
if you want your UI to update dynamically, you will need to implement INotifyPropertyChanged and call PropertyChanged("isEnabled") whenever _status changes

Related

Searching In DataGrid

I want to search in a data grid via typing in a textbox, but I am unable to find solution.
Do I need to do any binding? If so, then how do I do it?
If you want filter text in your Datagrid i.e by Name, try this...
private bool DataMatchesFilterText(User user, string filterText)
{
return user.Name.ToString() == filterText;
}
Yeah you will require your data grid to be bound to a Property that contains all your data.
Then add a event handler to your Textbox to act on one of the key events, e.g.
Xaml:
<TextBox x:Name="SearchBox" KeyUp="FilterTextBox_TextChanged" />
Then in the code behind you need to act on that event. Here you need to extract the filter text, get the rows in your DataGrid and then perform some method to determine if it should be visible or not. You will need to implement your own DataMatchesFilterText method.
Codebehind:
private void FilterTextBox_TextChanged(object sender, KeyEventArgs e)
{
var filterTextBox = (TextBox)sender;
var filterText = filterTextBox.Text;
SetRowVisibilityByFilterText(filterText);
}
private void SetRowVisibilityByFilterText(string filterText)
{
GetVisibleRows(yourGrid)
.ToList()
.ForEach(
x =>
{
if (x == null) return;
x.Visibility =
DataMatchesFilterText(x.Item as YourRowProperty, filterText) ? Visibility.Visible : Visibility.Collapsed;
});
}
public static IEnumerable<DataGridRow> GetVisibleRows(DataGrid grid)
{
if (grid == null || grid.Items == null) yield break;
int count = grid.ItemsSource == null
? grid.Items.Count
: grid.ItemsSource.Cast<object>().Count();
for (int i = 0; i < count; i++)
{
yield return (DataGridRow)grid.ItemContainerGenerator.ContainerFromIndex(i);
}
}

How to reload the ICollectionView binding to wpf combobox?

I have one textBox and one combobox in wpf usercontrol. ComboxBox is binded to ICollectionView (CurencyList) which populates Currency Pairs like GBP/EUR,CLP/EUR,USD/EUR,EUR/USD etc. Whenever the user writes in textbox e.g. EUR the combobox should get filtered and display the dropdownlist with EUR as the second currency.
For this I have used like:
public string Currency
{
get {
return _criteriaType.currency; }
set
{
if (_criteriaType.currency != value)
{
_criteriaType.currency = value.EmptyOrWhiteSpaceAsNull();
base.OnPropertyChanged("Currency");
CurrencyList.Filter = new Predicate<object>(Contains);
}
}
}
public bool Contains(object de)
{
CurrencyPair o = de as CurrencyPair;
if (Currency != null || Currency == string.Empty)
{
return (o.name.Substring(3, 4).ToLower().Contains(Currency.ToLower()));
}
else
{
IsOpen = false;
OnPropertyChanged("IsOpen");
return false;
}
}
CurrencyList is coming from a webservice:
private ICollectionView GetCurrencyPair()
{
strCurrencyPair.arg0 = (Currency != string.Empty && Currency != null) ? Convert.ToString(Currency).ToUpper() : string.Empty;
string[][] cPair = ServiceLocator.Resolve<IWebServiceRepository>().BusinessWebService.getCurrenyPairs(strCurrencyPair);
foreach (string[] item in cPair)
{
IList.Add(new CurrencyPair() { name = (Convert.ToString(item[0]).ToUpper() + "/" + Convert.ToString(item[1]).ToUpper()) });
}
return CurrencyList =CollectionViewSource.GetDefaultView(IList);
}
Filtering is working fine. But when the user deletes the currency from textbox with backward arrow key from keyboard, the combobox filtered to nothing i.e. dropdownlist is empty.
How to overcome this problem. kindly suggest?
you need to update your collection view through the text change event
private void OnTextChanged(object sender, Eventargs e) {
var vm = yourViewModelOrWhatEver;
((ICollectionView)vm.CurrencyList).Refresh();
}
or set the filter predicate once and fire only the refresh on currency change
public void ctor() {
CurrencyList.Filter = new Predicate<object>(Contains);
}
public string Currency {
get { return _criteriaType.currency; }
set {
if (_criteriaType.currency == value) {
return;
}
_criteriaType.currency = value.EmptyOrWhiteSpaceAsNull();
base.OnPropertyChanged("Currency");
CurrencyList.Refresh(); // refresh/filter the collection view
}
}
hope that helps
EDIT
you say
Filtering is working fine. But when the user deletes the currency from
textbox with backward arrow key from keyboard, the combobox filtered
to nothing i.e. dropdownlist is empty. How to overcome this problem.
kindly suggest?
then you must change your predicate function to this one
public bool Contains(object de)
{
CurrencyPair o = de as CurrencyPair;
if (Currency != null || Currency == string.Empty) {
// Currency == string.Empty should also true
return (Currency == string.Empty) || (o.name.Substring(3, 4).ToLower().Contains(Currency.ToLower()));
} else {
IsOpen = false;
OnPropertyChanged("IsOpen");
return false;
}
}
I would assume the issue lies with this statement: string[][] cPair =
ServiceLocator.Resolve().BusinessWebService.getCurrenyPairs(strCurrencyPair);
what is the return value from that service if you pass string.empty for strCurrencyPair as you will currently be doing once the textbox is empty from backspacing?

Wpf Datagrid Max Rows

I am currently working with data grid where I only want to allow the user to enter UP TO 20 rows of data before making CanUserAddRows to false.
I made a dependency property on my own datagrid (that derives from the original one). I tried using the event "ItemContainerGenerator.ItemsChanged" and check for row count in there and if row count = max rows, then make can user add row = false (I get an exception for that saying i'm not allow to change it during "Add Row".
I was wondering if there is a good way on implementing this ....
Thanks and Regards,
Kevin
Here's a subclassed DataGrid with a MaxRows Dependency Property. The things to note about the implementation is if the DataGrid is in edit mode and CanUserAddRows is changed an InvalidOperationException will occur (like you mentioned in the question).
InvalidOperationException
'NewItemPlaceholderPosition' is not
allowed during a transaction begun by
'AddNew'.
To workaround this, a method called IsInEditMode is called in OnItemsChanged and if it returns true we subscribe to the event LayoutUpdated to check IsInEditMode again.
Also, if MaxRows is set to 20, it must allow 20 rows when CanUserAddRows is True, and 19 rows when False (to make place for the NewItemPlaceHolder which isn't present on False).
It can be used like this
<local:MaxRowsDataGrid MaxRows="20"
CanUserAddRows="True"
...>
MaxRowsDataGrid
public class MaxRowsDataGrid : DataGrid
{
public static readonly DependencyProperty MaxRowsProperty =
DependencyProperty.Register("MaxRows",
typeof(int),
typeof(MaxRowsDataGrid),
new UIPropertyMetadata(0, MaxRowsPropertyChanged));
private static void MaxRowsPropertyChanged(DependencyObject source, DependencyPropertyChangedEventArgs e)
{
MaxRowsDataGrid maxRowsDataGrid = source as MaxRowsDataGrid;
maxRowsDataGrid.SetCanUserAddRowsState();
}
public int MaxRows
{
get { return (int)GetValue(MaxRowsProperty); }
set { SetValue(MaxRowsProperty, value); }
}
private bool m_changingState;
public MaxRowsDataGrid()
{
m_changingState = false;
}
protected override void OnItemsChanged(System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
{
base.OnItemsChanged(e);
if (IsInEditMode() == true)
{
EventHandler eventHandler = null;
eventHandler += new EventHandler(delegate
{
if (IsInEditMode() == false)
{
SetCanUserAddRowsState();
LayoutUpdated -= eventHandler;
}
});
LayoutUpdated += eventHandler;
}
else
{
SetCanUserAddRowsState();
}
}
private bool IsInEditMode()
{
IEditableCollectionView itemsView = Items;
if (itemsView.IsAddingNew == false && itemsView.IsEditingItem == false)
{
return false;
}
return true;
}
// This method will raise OnItemsChanged again
// because a NewItemPlaceHolder will be added or removed
// so to avoid infinite recursion a bool flag is added
private void SetCanUserAddRowsState()
{
if (m_changingState == false)
{
m_changingState = true;
int maxRows = (CanUserAddRows == true) ? MaxRows : MaxRows-1;
if (Items.Count > maxRows)
{
CanUserAddRows = false;
}
else
{
CanUserAddRows = true;
}
m_changingState = false;
}
}
}
I am an adept of K.I.S.S. To reduce the amount of lines required to do the job and keep it simple, use the following:
private void MyDataGrid_LoadingRow( object sender, DataGridRowEventArgs e )
{
// The user cannot add more rows than allowed
IEditableCollectionView itemsView = this.myDataGrid.Items;
if( this.myDataGrid.Items.Count == max_RowCount + 1 && itemsView.IsAddingNew == true )
{
// Commit the current one added by the user
itemsView.CommitNew();
// Once the adding transaction is commit the user cannot add an other one
this.myDataGrid.CanUserAddRows = false;
}
}
Simple and compact ;0)

Is there any way of checking if a DataGrid in Silverlight has Focus?

I have a Silverlight DataGrid of which I need to check if it has Focus. I know there is a method to set Focus and an event for GotFocus but can't see anyhting for checking if it has focus.
Any Ideas ?
AFAIK there is no direct method or property to check if it has focus, but you should be able to use the FocusManager.GetFocusedElement().
If you then define a extension method, you should be able to call MyDataGrid.HasFocus():
public static class ControlExtensions
{
public static bool HasFocus(this Control aControl)
{
return System.Windows.Input.FocusManager.GetFocusedElement() == aControl;
}
}
[edited: I did test it now:]
However there is catch: the call GetFocusedElement() can return the current focused cell within the DataGrid. So in that case the HasFocus will return false.
To be able to check if the DataGrid or one of its cells are focused, we can adapt our extension method like this
public static class ControlExtensions
{
public static bool HasFocus(this Control aControl, bool aCheckChildren)
{
var oFocused = System.Windows.Input.FocusManager.GetFocusedElement() as DependencyObject;
if (!aCheckChildren)
return oFocused == aControl;
while (oFocused != null)
{
if (oFocused == aControl)
return true;
oFocused = System.Windows.Media.VisualTreeHelper.GetParent(oFocused);
}
return false;
}
}
Hope this helps a bit?

Detecting WPF Validation Errors

In WPF you can setup validation based on errors thrown in your Data Layer during Data Binding using the ExceptionValidationRule or DataErrorValidationRule.
Suppose you had a bunch of controls set up this way and you had a Save button. When the user clicks the Save button, you need to make sure there are no validation errors before proceeding with the save. If there are validation errors, you want to holler at them.
In WPF, how do you find out if any of your Data Bound controls have validation errors set?
This post was extremely helpful. Thanks to all who contributed. Here is a LINQ version that you will either love or hate.
private void CanExecute(object sender, CanExecuteRoutedEventArgs e)
{
e.CanExecute = IsValid(sender as DependencyObject);
}
private bool IsValid(DependencyObject obj)
{
// The dependency object is valid if it has no errors and all
// of its children (that are dependency objects) are error-free.
return !Validation.GetHasError(obj) &&
LogicalTreeHelper.GetChildren(obj)
.OfType<DependencyObject>()
.All(IsValid);
}
The following code (from Programming WPF book by Chris Sell & Ian Griffiths) validates all binding rules on a dependency object and its children:
public static class Validator
{
public static bool IsValid(DependencyObject parent)
{
// Validate all the bindings on the parent
bool valid = true;
LocalValueEnumerator localValues = parent.GetLocalValueEnumerator();
while (localValues.MoveNext())
{
LocalValueEntry entry = localValues.Current;
if (BindingOperations.IsDataBound(parent, entry.Property))
{
Binding binding = BindingOperations.GetBinding(parent, entry.Property);
foreach (ValidationRule rule in binding.ValidationRules)
{
ValidationResult result = rule.Validate(parent.GetValue(entry.Property), null);
if (!result.IsValid)
{
BindingExpression expression = BindingOperations.GetBindingExpression(parent, entry.Property);
System.Windows.Controls.Validation.MarkInvalid(expression, new ValidationError(rule, expression, result.ErrorContent, null));
valid = false;
}
}
}
}
// Validate all the bindings on the children
for (int i = 0; i != VisualTreeHelper.GetChildrenCount(parent); ++i)
{
DependencyObject child = VisualTreeHelper.GetChild(parent, i);
if (!IsValid(child)) { valid = false; }
}
return valid;
}
}
You can call this in your save button click event handler like this in your page/window
private void saveButton_Click(object sender, RoutedEventArgs e)
{
if (Validator.IsValid(this)) // is valid
{
....
}
}
The posted code did not work for me when using a ListBox. I rewrote it and now it works:
public static bool IsValid(DependencyObject parent)
{
if (Validation.GetHasError(parent))
return false;
// Validate all the bindings on the children
for (int i = 0; i != VisualTreeHelper.GetChildrenCount(parent); ++i)
{
DependencyObject child = VisualTreeHelper.GetChild(parent, i);
if (!IsValid(child)) { return false; }
}
return true;
}
Had the same problem and tried the provided solutions. A combination of H-Man2's and skiba_k's solutions worked almost fine for me, for one exception: My Window has a TabControl. And the validation rules only get evaluated for the TabItem that is currently visible. So I replaced VisualTreeHelper by LogicalTreeHelper. Now it works.
public static bool IsValid(DependencyObject parent)
{
// Validate all the bindings on the parent
bool valid = true;
LocalValueEnumerator localValues = parent.GetLocalValueEnumerator();
while (localValues.MoveNext())
{
LocalValueEntry entry = localValues.Current;
if (BindingOperations.IsDataBound(parent, entry.Property))
{
Binding binding = BindingOperations.GetBinding(parent, entry.Property);
if (binding.ValidationRules.Count > 0)
{
BindingExpression expression = BindingOperations.GetBindingExpression(parent, entry.Property);
expression.UpdateSource();
if (expression.HasError)
{
valid = false;
}
}
}
}
// Validate all the bindings on the children
System.Collections.IEnumerable children = LogicalTreeHelper.GetChildren(parent);
foreach (object obj in children)
{
if (obj is DependencyObject)
{
DependencyObject child = (DependencyObject)obj;
if (!IsValid(child)) { valid = false; }
}
}
return valid;
}
In addition to the great LINQ-implementation of Dean, I had fun wrapping the code into an extension for DependencyObjects:
public static bool IsValid(this DependencyObject instance)
{
// Validate recursivly
return !Validation.GetHasError(instance) && LogicalTreeHelper.GetChildren(instance).OfType<DependencyObject>().All(child => child.IsValid());
}
This makes it extremely nice considering reuseablity.
I would offer a small optimization.
If you do this many times over the same controls, you can add the above code to keep a list of controls that actually have validation rules. Then whenever you need to check for validity, only go over those controls, instead of the whole visual tree.
This would prove to be much better if you have many such controls.
Here is a library for form validation in WPF. Nuget package here.
Sample:
<Border BorderBrush="{Binding Path=(validationScope:Scope.HasErrors),
Converter={local:BoolToBrushConverter},
ElementName=Form}"
BorderThickness="1">
<StackPanel x:Name="Form" validationScope:Scope.ForInputTypes="{x:Static validationScope:InputTypeCollection.Default}">
<TextBox Text="{Binding SomeProperty}" />
<TextBox Text="{Binding SomeOtherProperty}" />
</StackPanel>
</Border>
The idea is that we define a validation scope via the attached property telling it what input controls to track.
Then we can do:
<ItemsControl ItemsSource="{Binding Path=(validationScope:Scope.Errors),
ElementName=Form}">
<ItemsControl.ItemTemplate>
<DataTemplate DataType="{x:Type ValidationError}">
<TextBlock Foreground="Red"
Text="{Binding ErrorContent}" />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
You can iterate over all your controls tree recursively and check the attached property Validation.HasErrorProperty, then focus on the first one you find in it.
you can also use many already-written solutions
you can check this thread for an example and more information
You might be interested in the BookLibrary sample application of the WPF Application Framework (WAF). It shows how to use validation in WPF and how to control the Save button when validation errors exists.
In answer form aogan, instead of explicitly iterate through validation rules, better just invoke expression.UpdateSource():
if (BindingOperations.IsDataBound(parent, entry.Property))
{
Binding binding = BindingOperations.GetBinding(parent, entry.Property);
if (binding.ValidationRules.Count > 0)
{
BindingExpression expression
= BindingOperations.GetBindingExpression(parent, entry.Property);
expression.UpdateSource();
if (expression.HasError) valid = false;
}
}
I am using a DataGrid, and the normal code above did not find errors until the DataGrid itself lost focus. Even with the code below, it still doesn't "see" an error until the row loses focus, but that's at least better than waiting until the grid loses focus.
This version also tracks all errors in a string list. Most of the other version in this post do not do that, so they can stop on the first error.
public static List<string> Errors { get; set; } = new();
public static bool IsValid(this DependencyObject parent)
{
Errors.Clear();
return IsValidInternal(parent);
}
private static bool IsValidInternal(DependencyObject parent)
{
// Validate all the bindings on this instance
bool valid = true;
if (Validation.GetHasError(parent) ||
GetRowsHasError(parent))
{
valid = false;
/*
* Find the error message and log it in the Errors list.
*/
foreach (var error in Validation.GetErrors(parent))
{
if (error.ErrorContent is string errorMessage)
{
Errors.Add(errorMessage);
}
else
{
if (parent is Control control)
{
Errors.Add($"<unknow error> on field `{control.Name}`");
}
else
{
Errors.Add("<unknow error>");
}
}
}
}
// Validate all the bindings on the children
for (int i = 0; i != VisualTreeHelper.GetChildrenCount(parent); i++)
{
var child = VisualTreeHelper.GetChild(parent, i);
if (IsValidInternal(child) == false)
{
valid = false;
}
}
return valid;
}
private static bool GetRowsHasError(DependencyObject parent)
{
DataGridRow dataGridRow;
if (parent is not DataGrid dataGrid)
{
/*
* This is not a DataGrid, so return and say we do not have an error.
* Errors for this object will be checked by the normal check instead.
*/
return false;
}
foreach (var item in dataGrid.Items)
{
/*
* Not sure why, but under some conditions I was returned a null dataGridRow
* so I had to test for it.
*/
dataGridRow = (DataGridRow)dataGrid.ItemContainerGenerator.ContainerFromItem(item);
if (dataGridRow != null &&
Validation.GetHasError(dataGridRow))
{
return true;
}
}
return false;
}

Resources