I'm struggling with weird issue in my WPF code. I have a combobox, which allows user to choose one of its options. Each item in this combobox is some kind of string.Format() pattern. For example when user chooses option 'Hello {0} world', my code generates two TextBlocks with 'Hello' and 'world' and one TextBox between them, where user can provide his input.
Here's my xaml:
<ComboBox ItemsSource="{Binding PossiblePatternOptions}" DisplayMemberPath="Pattern" SelectedItem="{Binding SelectedPattern, ValidatesOnDataErrors=True}" Width="250" Margin="5,0,25,0"/>
<ItemsControl ItemsSource="{Binding SelectedPattern.GeneratedControls}"/>
SelectedPattern.GeneratedControls is an ObservableCollection<UIElement>:
public ObservableCollection<UIElement> GeneratedControls
{
get
{
return _generatedControls ?? (_generateControls = new ObservableCollection<UIElement>(GenerateControls(_splittedPattern)));
}
}
Here's how I create new TextBox (in GenerateControls method):
var placeholderChunk = chunk as TextBoxPlaceholder;
var textBox = new TextBox();
textBox.ToolTip = placeholderChunk.Description;
Binding binding = new Binding("Value");
binding.ValidatesOnDataErrors = true;
binding.Source = placeholderChunk;
binding.UpdateSourceTrigger = UpdateSourceTrigger.PropertyChanged;
textBox.SetBinding(TextBox.TextProperty, binding);
TextBoxPlaceholder implements IDataErrorInfo and provides error details for incorrect input:
public class TextBoxPlaceholder : IDataErrorInfo
{
public string Value {get;set;}
public string this[string columnName]
{
get
{
switch (columnName)
{
case "Value":
return string.IsNullOrEmpty(Value) ? "Error" : string.Empty;
default:
return string.Empty;
}
}
}
public string Error
{
get { throw new NotImplementedException(); }
}
}
The problem is that when I choose an option from combobox for the first time, generated TextBoxes are validated correctly and they get nice red frame around them, but when I choose an option that has been chosen previously, no validation occurs and there is no red frame anymore. I noticed that when I change code in GeneratedControls property so it recreates collection every time, it works ok. What could possibly be the problem here?
I know it may be explained poorly, in case of any misunderstandings I will clarify.
It seems your "Value" property does not fire an update when it changes, so the text binding of the text box cannot react on the change and will not evaluate the value again. Try something like this:
// implmeent INotifyPropertyChanged
public class TextBoxPlaceholder : IDataErrorInfo, System.ComponentModel.INotifyPropertyChanged
{
private string mValue;
public string Value
{
get{ return mValue; }
// fire notification
set{mValue = value;NotifyPropertyChanged("Value");}
}
public event PropertyChangedEventHandler PropertyChanged;
// helper method
private void NotifyPropertyChanged(String info)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(info));
}
}
// your code goes here
Related
I cannot get the picker to signal my MV when it gains focus.
I am trying to get a filtered list to populate a Picker through MVVM (after the user enters a string into a textbox to filter the list).
My setup is a screen that will have a textbox that can be used to enter a string (which will be used to filter the list). The picker is populated by querying a local db and returning that as a list. At first, I was rebuilding that list on every keystroke (by requerying the db). That worked, but the lag between keystrokes was unacceptable.
I tried to NOT build the list between keystrokes, but ONLY when the textbox lost focus. Finally was able to make that work by using an unfocused event trigger to tell the VM that the textbox was unfocused (done through MessagingCenter). At this time, I also decided to not query the db for the list, but to use an ObservableCollection, thinking that when the OC is filtered, it would automatically update the picker list.
This worked (kinda), but if you typed a string into the textbox and then clicked the picker, the picker list was populated BEFORE the unfocused event fired (I think). I only say that because the list was always one step behind. In other words, if you entered 'ABC' and clicked the picker, the list still had ALL items in the list. But if you went back to the textbox and replaced the string with 'XYZ' and clicked the picker again, the list populated with ONLY items that included 'ABC'. If you were to enter a string in the textbox, click on ANY other control (besides the picker), and then click the picker, the list would be correct.
So that got me to thinking that maybe the 'focused' event is firing before 'unfocused', and frankly, it makes more sense to build that list when the picker is selected anyway (since that's where it will be needed). However, when I move the trigger to the picker control, I get errors when the events are fired. The build is fine, and everything works until the picker gains control, when it then crashes.
FWIW, here is the code for the picker (with trigger):
<!-- (other code) -->
<Picker ItemsSource="{Binding myList}" Title="Select a specification" SelectedItem="{Binding mySelectedItem}" Grid.Row="1" Grid.Column="1" Grid.ColumnSpan="3">
<Picker.Triggers>
<EventTrigger Event="Focused">
<local:FocusedTriggerAction />
</EventTrigger>
</Picker.Triggers>
<Picker.ItemDisplayBinding>
<Binding Path="FullName"/>
</Picker.ItemDisplayBinding>
</Picker>
<!-- (other code) -->
The code for the 'FocusedTriggerAction':
namespace DatabaseTest
{
public class FocusedTriggerAction : TriggerAction<Entry>
{
protected override void Invoke(Entry entry)
{
MessagingCenter.Send<FocusedTriggerAction>(this, "changeList");
}
}
}
And the VM:
namespace DatabaseTest.ViewModels
{
public class MyViewModel : INotifyPropertyChanged
{
public MyViewModel()
{
List<MyOptions> mystuff = new List<MyOptions>();
using (SQLite.SQLiteConnection conn = new SQLite.SQLiteConnection(App.DB_PATH))
{
conn.CreateTable<MyOptions>();
mystuff = conn.Table<MyOptions>().ToList();
}
myList = new ObservableCollection<MyOptions>(mystuff);
MessagingCenter.Subscribe<FocusedTriggerAction> (this, "changeList", (sender) =>
{
if (mylookupstring == "")
testme = 1;
else
myList = new ObservableCollection<MyOptions>(mystuff.Where(o => o.SpecFullName.Contains(mylookupstring)));
});
}
string mylookupstring = string.Empty;
string myselectedthing;
MyOptions myselectedpickeritem;
int testme = 0;
int foundselecteditem = 0;
public event PropertyChangedEventHandler PropertyChanged;
void OnPropertyChanged([CallerMemberName] string name = "")
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
}
public string myLookupString
{
get { return mylookupstring; }
set
{
mylookupstring = value;
mylookupstring = mylookupstring.ToUpper();
OnPropertyChanged();
}
}
private ObservableCollection<MyOptions> _mylist;
public ObservableCollection<MyOptions> myList
{
get { return _mylist; }
set
{
if (_mylist != value)
{
_mylist = value;
OnPropertyChanged();
}
}
}
//...Other code...
}
}
For anyone else that might need help with this issue, I figured out the issue with getting the Picker to trigger correctly (with the help of a fellow reader here). When I moved the trigger from the entry control to the picker, I forgot to change the 'entry' parts to 'picker' in the code-behind.
I had to change this:
namespace DatabaseTest
{
public class FocusedTriggerAction : TriggerAction<Entry>
{
protected override void Invoke(Entry entry)
{
MessagingCenter.Send<FocusedTriggerAction>(this, "changeList");
}
}
}
To this:
namespace DatabaseTest
{
public class FocusedTriggerAction : TriggerAction<Picker>
{
protected override void Invoke(Picker sender)
{
MessagingCenter.Send<FocusedTriggerAction>(this, "changeList");
}
}
}
I have a ComboBox that is bound to a property on my ViewModel (from hear on "VM".) When a user makes a selection on the ComboBox it properly updates the bound property in my VM. Within my UI code, I have subscribed to the PropertyChanged event on my VM.
As it should behave, when the user makes a selection within the ComboBox, my PropertyChanged event is correctly executing in my UI back-end code. When the UI code catches the change of this property, under certain selection conditions I need to halt the process and request the user for additional information. From the UI, I send them a dialog. If they cancel the dialog, I reset the value in the VM that is associated with the ComboBox controls SelectedValue.
This is what I've observed. When the operation is cancelled by the user, my VM property is being set to the new value. However, the ComboBox is still showing the text value of the original entry that has now changed. How can I force the ComboBox to update itself from within my PropertyChanged event? In this case, I think it's just a textual issue or numeric index change that's referencing the text data from the bound collection. The data is correct in the VM but the display value for the ComboBox is wrong.
EXAMPLE
ComboBox Details
<ComboBox
ItemsSource="{Binding ListOfComboBoxDisplayObjects}"
SelectedValue="{Binding MySelectionIsAnEnumeration}"
DisplayMemberPath="Text"
SelectedValuePath="EnumerationValue"
Height="27" />
Sorry for the wordy properties on the VM, but that's to explain what's happening. My ListOfComboBoxDisplayObjects collection represents a set of enumerator values that are stored in the path within SelectedValuePath. The descriptive text for each value is pulled from the ListOfComboBoxDisplayObjects which is a special list strictly created for the UI. This basically pairs an enumeration value with a meaningful description.
ListOfComboBoxDisplayObjects Definition (from within VM)
Edit #1 - Added this definition to my example
private ObservableCollection<BindableEnumerationItem<Values>> _listOfComboBoxDisplayObjects;
public ObservableCollection<BindableEnumerationItem<Values>> ListOfComboBoxDisplayObjects
{
get { return _listOfComboBoxDisplayObjects; }
private set
{
if (value != _listOfComboBoxDisplayObjects)
{
_listOfComboBoxDisplayObjects= value;
PropertyChanged(this, new PropertyChangedEventArgs(nameof(ListOfComboBoxDisplayObjects)));
}
}
}
MySelectionIsAnEnumeration Definition (From within VM)
*Edit #1: Adding this code definition.
private Values_mySelectionIsAnEnumeration ;
public Values MySelectionIsAnEnumeration
{
get { return _mySelectionIsAnEnumeration; }
set
{
//Double-checked this-- value is different on the second-call to change this value, once the UI cancels the operation.
if (value != _mySelectionIsAnEnumeration)
{
_mySelectionIsAnEnumeration= value;
PropertyChanged(this, new PropertyChangedEventArgs(nameof(MySelectionIsAnEnumeration )));
}
}
}
Pertinent Values Associated with ListOfComboBoxDisplayObjects
These values are generated in the ctor of the VM. They are fixed throughout the application.
Item #1
Text: "This is a Foo!"
Value: Values.Foo
Item #2:
Text: "Hi, I'm Bar."
Value: Values.Bar
Item #3:
Text: "This is Baz. I need to ask a question before I can be used."
Value: Values.Baz
PropertyChanged Event - From the UI Back-End
private void VM_PropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
{
switch (e.PropertyName)
{
case "MySelectionIsAnEnumeration":
if (VM.MySelectionIsAnEnumeration == Values.Baz)
{
//Prompt the user and get DialogResult.
bool answerGiven = AskAQuestionAndGetAResult();
if(!answerGiven)
VM.MySelectionIsAnEnumeration = Values.Foo;
}
break;
}
}
After executing the above code, what I'm observing is that the VM.MySelectionIsAnEnumeration value is indeed changing to the proper value of Value.Foo when a user cancels the operation within AskAQuestionAndGetAResult(). However, after it's finished the ComboBox still reads "This is Baz. I need to ask a question before I can be used.", which is obviously the display value associated with Value.Baz.
How can I update both the underlying VM property AND the display text on the CombobBox to correctly show the valued that is now stored in VM.MySelectionIsAnEnumeration?
Edit #2
Below is the code efor my BindableEnumerationItem that I use within my Observable Collections for comboxes and list boxes. This is used throughout my application in simpler cases and has caused no issue. Please note, this is my actual, unaltered code. I've not renamed anything. My comboboxes can bind to each Item property for a type-safe property and DisplayText is the descriptor text.
public class BindableEnumerationItem<T> : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged = delegate { };
private T _item;
public BindableEnumerationItem(T item, string displayText)
{
_item = item;
_displayText = displayText;
}
private string _displayText;
public string DisplayText
{
get { return _displayText; }
set
{
if (value != _displayText)
{
_displayText = value;
PropertyChanged(this, new PropertyChangedEventArgs("DisplayText"));
}
}
}
public T Item
{
get { return _item; }
set
{
_item = value;
PropertyChanged(this, new PropertyChangedEventArgs("Item"));
}
}
}
create an extension that will wire up the command from your viewmodel in xaml to the selector, which in this case is the combobox.
public partial class Extensions
{
public static readonly DependencyProperty SelectionChangedCommandProperty = DependencyProperty.RegisterAttached("SelectionChangedCommand", typeof(ICommand), typeof(Extensions), new UIPropertyMetadata((s, e) =>
{
var element = s as Selector;
if (element != null)
{
element.SelectionChanged -= OnSelectionChanged;
if (e.NewValue != null)
{
element.SelectionChanged += OnSelectionChanged;
}
}
}));
public static ICommand GetSelectionChangedCommand(UIElement element)
{
return (ICommand)element.GetValue(SelectionChangedCommandProperty);
}
public static void SetSelectionChangedCommand(UIElement element, ICommand value)
{
element.SetValue(SelectionChangedCommandProperty, value);
}
private static void OnSelectionChanged(object sender, SelectionChangedEventArgs e)
{
var element = sender as Selector;
var command = element.GetValue(SelectionChangedCommandProperty) as ICommand;
if (command != null && command.CanExecute(element.SelectedItem))
{
command.Execute(element.SelectedItem);
e.Handled = true;
}
}
}
Create the command in the viewmodel that handles the value changed event.
public ICommand EnumerationValueChangedCommand
{
get
{
return new Command(
() =>
{
if (VM.MySelectionIsAnEnumeration == Values.Baz)
{
//Prompt the user and get DialogResult.
bool answerGiven = AskAQuestionAndGetAResult();
if (!answerGiven)
VM.MySelectionIsAnEnumeration = Values.Foo;
}
});
}
}
And then bind using that extension. ext is the namespace for your extensions.
<ComboBox
ItemsSource="{Binding ListOfComboBoxDisplayObjects}"
SelectedValue="{Binding MySelectionIsAnEnumeration}"
DisplayMemberPath="Text"
SelectedValuePath="EnumerationValue"
ext:Extensions.SelectionChangedCommand="{Binding EnumerationValueChangedCommand}"
Height="27" />
My data grid has in itemsSource a list of Groups:
public class Group : INotifyPropertyChanged
{
public Group() { }
public Group(int groupID, string groupName)
{
this.GroupID = groupID;
this.GroupName = groupName;
}
private int _groupID;
public int GroupID
{
get { return _groupID; }
set
{
_groupID = value;
OnPropertyChanged("GroupID");
}
}
private string _groupName;
public string GroupName
{
get { return _groupName; }
set
{
_groupName = value;
OnPropertyChanged("GroupName");
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string property)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(property));
}
}
}
But I realize that when I edit a cell, I need to press Enter key to fired OnPropertyChanged from Group class. So if I only edit the cell value don't fire the event unless I press Enter key.
Is possible when I edit the cell value without press Enter key, get fired the event?
You need to change the default two-way binding to be UpdateSourceTrigger="PropertyChanged".
Example from MSDN:
<TextBox Name="itemNameTextBox"
Text="{Binding Path=ItemName, UpdateSourceTrigger=PropertyChanged}" />
Example: http://msdn.microsoft.com/en-us/library/system.windows.data.binding.updatesourcetrigger.aspx
UpdateSourceTrigger Binding Property Page: http://msdn.microsoft.com/en-us/library/system.windows.data.updatesourcetrigger.aspx
You need to use UpdateSourcetrigger within your xaml
There are three different kinds:
PropertyChanged – The source is updated whenever the target property value
changes.
LostFocus – The source is updated when target property changes and target object looses focus.
Explicit – The source is updated when explicit call is made to update using “BindingExpression.UpdateSource”.
By default the WPF DataGrid will commit a row when focus is lost on the row, the ‘Enter’ key is pressed, tabbing to the next row, or programmatically calling commit on the row.
some more information you get here
I am trying to implement data binding, and to have TextBox's text to be update once I click on some button.
XAML:
<TextBox Text="{Binding Path=Output}" />
Code:
public MainWindow()
{
InitializeComponent();
DataContext = Search;
Search.Output = "111";
}
public SearchClass Search = new SearchClass();
private void button1_Click(object sender, RoutedEventArgs e)
{
Search.Output = "222";
}
public class SearchClass
{
string _output;
public string Output
{
get { return _output; }
set { _output = value; }
}
}
When I execute the program, I see "111", so the binding from MainWindow() works, but if I click a button - the text in the TextBox is not updated (but in the debugger I see that button1_Click is executed and Search.Output is now equal to "222"). What am I doing wrong?
You should implement INotifyPropertyChanged in your SearchClass and then in setter raise the event:
public event PropertyChangedEventHandler PropertyChanged = delegate { };
public string Output
{
get { return _output; }
set
{
_output = value;
PropertyChanged(this, new PropertyChangedEventArgs("Output"));
}
}
If I understood right, SearchClass is the DataContext for your TextBlock. In this case implementing as above would help.
When WPF see some class as the source of Binding - it tries to cast it to INotifyPropertyChanged and subscribe to PropertyChanged event. And when event is raised - WPF updates the binding associated with sender (first argument of PropertyChanged). It is the main mechanism that makes binding work so smoothly.
You have to implement the INotifyPropertyChanged interface on your SearchClass class. This is how binder values are notified their source values have changed. It displays the "111" value because it hasn't been laid out yet (more or less), but will won't update after that until you implement that interface.
say I have this control:
public partial class bloc999 : UserControl
{
bloc999Data mainBlock = new bloc999Data();
public bloc999()
{
InitializeComponent();
mainBlock.txtContents = "100";
base.DataContext = mainBlock;
}
}
in the xaml:
<TextBox Margin="74,116,106,0" Name="txtContents"
Text="{Binding Path=txtContents, UpdateSourceTrigger=PropertyChanged,Mode = TwoWay}" />
<TextBox Margin="74,145,106,132" Name="txtContents2"
Text="{Binding Path=txtContents2, UpdateSourceTrigger=PropertyChanged,Mode = TwoWay}" />
Then I have this class:
public class bloc999Data : INotifyPropertyChanged
{
string _txtContents;
string _txtContents2;
public event PropertyChangedEventHandler PropertyChanged;
void NotifyPropertyChanged(string propName)
{
if (this.PropertyChanged != null)
this.PropertyChanged(
this, new PropertyChangedEventArgs(propName));
}
public string txtContents2
{
get
{
return this._txtContents2;
}
set
{
if (int.Parse(value) > int.Parse(this._txtContents))
{
this._txtContents2 = "000";
}
else
this._txtContents2 = value;
NotifyPropertyChanged("txtContents2");
}
}
public string txtContents
{
get
{
return this._txtContents;
}
set
{
this._txtContents = value;
NotifyPropertyChanged("txtContents");
}
}
}
Ok now say I have A button on the form and I do this in the code:
mainBlock.txtContents2 = "7777777";
It puts 000 in the textbox, but If i just type in manually, in the textbox (txtContents2), the setter code is called but for some reason the textboxes value does not change, the instance value does change. help?
I believe it's just because the value is changing within the context of the data binding operation, so WPF just ignores it because it knows the value is changing and thinks the event is superfluous. What it doesn't know is that you've gone and changed the value from the value WPF has to something else again.
If you do the notification in a separate message then WPF will process it outside the context of the current data binding operation and will thus pick up the change:
if (int.Parse(value) > int.Parse(this._txtContents))
{
this._txtContents2 = "000";
// notify WPF of our change to the property in a separate message
Dispatcher.BeginInvoke((ThreadStart)delegate
{
NotifyPropertyChanged("txtContents2");
});
}
else
{
this._txtContents2 = value;
NotifyPropertyChanged("txtContents2");
}
This assumes your view model has access to the Dispatcher. An example of how to do so is shown in my blog post on a base ViewModel class.
I was having similar problem earlier here
In your usercontrol, update Binding and set UpdateSourceTrigger to Explicit
<TextBox Margin="74,145,106,132" x:Name="txtContents2" TextChanged="txtContents2_TextChanged"
Text="{Binding Path=txtContents2, UpdateSourceTrigger=Explicit,Mode = TwoWay}" />
then in the TextChanged event handler update the binding manually by validating the input.
move validation logic from property txtContent2's setter in bloc999Data in this event handler
private void txtContents2_TextChanged(object sender, TextChangedEventArgs e)
{
if (int.Parse(txtContents2.Text) > int.Parse(mainBlock.txtContents))
{
mainBlock.txtContents2 = "000";
txtContents2.GetBindingExpression(TextBox.TextProperty).UpdateTarget();
}
else
{
mainBlock.txtContents2 = txtContents2.Text;
txtContents2.GetBindingExpression(TextBox.TextProperty).UpdateSource();
}
}
and it works.
Hope it helps!!