I have an object that is created, and want to bind to a property of that object through the mode OneWayToSource explicitly. This binding however is not working at all. It also has a red border around the textbox right when the program is initialized, when I only want the input validated once I click the button. My last ditch effor was embedding the source to the element itself, but no such luck. Here is what I have:
<StackPanel.Resources>
<my:HoursWorked x:Key="hwViewSource" />
</StackPanel.Resources>
<TextBox Style="{StaticResource textBoundStyle}" Name="adminTimeTxtBox">
<Binding Source="{StaticResource hwViewSource}" Path="Hours" UpdateSourceTrigger="PropertyChanged" Mode="OneWayToSource">
<Binding.ValidationRules>
<my:NumberValidationRule ErrorMessage="Please enter a number in hours." />
</Binding.ValidationRules>
</Binding>
</TextBox>
The HoursWorked object looks like this:
//I have omitted a lot of things so it's more readable
public class HoursWorked : INotifyPropertyChanged
{
private double hours;
public HoursWorked()
{
hours = 0;
}
public double Hours
{
get { return hours; }
set
{
if (Hours != value)
{
hours = value;
OnPropertyChanged("Hours");
}
}
}
#region Databinding
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(String info)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(info));
}
}
#endregion
}
once the window is initialized, this is the portion of code I have:
public partial class Blah : Window
{
private HoursWorked newLog;
public Blah()
{
InitializeComponent();
newLog = new HoursWorked();
adminTimeTxtBox.DataContext = newLog;
}
private void addAdBtn_Click(object sender, RoutedEventArgs e)
{
AddHours();
}
private void AddHours()
{
if (emp.UserType.Name == "Admin")
{
if(!ValidateElement.HasError(adminTimeTxtBox))
{
item.TimeLog.Add(newLog);
UpdateTotals();
adminTimeTxtBox.Clear();
}
}
}
}
and finally ValidateElement looks like this:
public static class ValidateElement
{
public static bool HasError(DependencyObject node)
{
bool result = false;
if (node is TextBox)
{
TextBox item = node as TextBox;
BindingExpression be = item.GetBindingExpression(TextBox.TextProperty);
be.UpdateSource();
}
if (Validation.GetHasError(node))
{
// If the dependency object is invalid, and it can receive the focus,
// set the focus
if (node is IInputElement) Keyboard.Focus((IInputElement)node);
result = true;
}
return result;
}
}
It validates properly, but every time I check to see if the property updates, it doesn't. I really need help on this, any help would be greatly appreciated.
You've got 2 instances of the HoursWorked class.
One is created in Resources via this tag <my:HoursWorked x:Key="hwViewSource" /> but then you create one in your Window with newLog = new HoursWorked(); and set it into the DataContext of the adminTimeTxtBox...so the one your Binding to (the Resource one) isn't the same as the one you're updating (the one inside Window).
You could change the Binding to
<Binding Source="{Binding}" ....
and then don't need the one defined in Resource.
TextBox.Text property is of type string, your Hours property is double.
You have to create a ValueConverter or an auxiliary property for parsing the string to double and vice versa.
Related
My simple program has two windows:
from the first one I set a Boolean value, which then...
I'll use in the second window to disable a number of TextBoxes depending on the aforementioned value itself.
Said TextBoxes are also characterized by a validation binding. At now my validation task is flawlessly working, but I'm not able to make the binding to the IsEnabled TextBox property work.
This is the snippet of my XAML containing one of the TextBoxes (for now the only one I've bound):
<TextBox x:Name="tbSlave1" Validation.Error="ValidationError" IsEnabled="{Binding TextBoxEnabled}" Text="{Binding UpdateSourceTrigger=PropertyChanged, Path=SlavePoint1Name, ValidatesOnDataErrors=true, NotifyOnValidationError=true}"/>
While this is my second window class:
public partial class GeneratorWindow : Window, INotifyPropertyChanged
{
private readonly Validator validator = new Validator();
private int noOfErrorsOnScreen;
public GeneratorWindow()
{
this.InitializeComponent();
this.grid.DataContext = this.validator;
}
public int NumberOfPoints { private get; set; }
public int MainPDC { private get; set; }
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(String info)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(info));
}
}
private Boolean IsEnabled;
public Boolean TextBoxEnabled
{
get { return IsEnabled; }
set
{
IsEnabled = value;
NotifyPropertyChanged("TextBoxEnabled");
}
}
private void ValidationError(object sender, ValidationErrorEventArgs eventArgs)
{
if (eventArgs.Action == ValidationErrorEventAction.Added)
{
this.noOfErrorsOnScreen++;
}
else
{
this.noOfErrorsOnScreen--;
}
}
private void ValidationCanBeExecuted(object sender, CanExecuteRoutedEventArgs eventArgs)
{
eventArgs.CanExecute = this.noOfErrorsOnScreen == 0;
eventArgs.Handled = true;
}
private void ValidationExecuted(object sender, ExecutedRoutedEventArgs eventArgs)
{
// If the validation was successful, let's generate the files.
this.Close();
eventArgs.Handled = true;
}
}
For now, what I'm getting back is that my window is disabled (can't select any TextBox) and, obviously, this:
System.Windows.Data Error: 40 : BindingExpression path error: 'TextBoxEnabled' property not found on 'object' ''Validator' (HashCode=14499481)'. BindingExpression:Path=TextBoxEnabled; DataItem='Validator' (HashCode=14499481); target element is 'TextBox' (Name='tbSlave1'); target property is 'IsEnabled' (type 'Boolean')
From what I can understand, the culprit is the way I'm managing the DataContext in my class constructor. I probably need to add something to the validator line or totally change it, but I can't understand how.
I think you should be fine if you set the DataContext to the GeneratorWindow and updated you bindings accordingly. For that to work you need to change Validator to a public property.
Changed Validator definition:
public Validator Validator { get; } = new Validator();
DataContext:
this.grid.DataContext = this;
Updated Binding:
<TextBox x:Name="tbSlave1"
Validation.Error="ValidationError"
IsEnabled="{Binding TextBoxEnabled}"
Text="{Binding UpdateSourceTrigger=PropertyChanged,
Path=Validator.SlavePoint1Name,
ValidatesOnDataErrors=true,
NotifyOnValidationError=true}"/>
Iv'e got a code that restricts textbox input to numbers, dashes and spaces.It works fine in the window that contains the textbox.
public static bool IsTextAllowed(string text)
{
//regex that matches disallowed text
Regex regex = new Regex("[^0-9,-]+");
return !regex.IsMatch(text);
}
private void textbox1_PreviewTextInput(object sender, TextCompositionEventArgs e)
{
e.Handled = !IsTextAllowed(e.Text);
}
However what I wish to do is put the code into a class so I can access it from multiple windows and pages. That's the part that isn't going so well.
I created a class called 'rules' and copied the bool method into it. I couldn't figure out a way to get the event handler to work in the class though so instead i tried to pass the string value produced by the method to another string, and bind it to 'textbox1'.
Here's the code.
public class rules : INotifyPropertyChanged
{
// String to contain passed on value from the regrex code
string integers;
//Method to pass on value to the string 'integers'
public bool this[string Digits]
{
get
{
//regex that matches disallowed text
Regex regex = new Regex("[^0-9,-]+");
return !regex.IsMatch(integers);
}
}
//Binding
public string Number
{
get
{
return integers;
}
set
{
if (integers != value)
{
integers = value;
RaisePropertyChanged("Number");
}
}
}
private void RaisePropertyChanged(string prop)
{
if (PropertyChanged != null)
{
PropertyChanged (this, new PropertyChangedEventArgs(prop));
}
}
public event PropertyChangedEventHandler PropertyChanged;
}
In the code behind the window I have 'textbox1' in I set the datacontext.
public signup()
{
InitializeComponent();
Rules = new rules();
this.DataContext = Rules;
}
Then I bind 'textbox1' to it in the xaml:
<TextBox x:Name="textbox1" Text="{Binding Number, Mode=TwoWay, ValidatesOnDataErrors=True, UpdateSourceTrigger=PropertyChanged}" />
Evidently I'm doing something wrong as it accepts any character punched in.
Is there anyway around this?
In the preview you are cancelling the input with the e.Handled
In the setter you are allowing the input
The TextBox still has value - you have done nothing to reset value
This should work
By calling RaisePropertyChanged("Number"); it should reset back if !IsTextAllowed(value)
if (integers == value) return;
if (IsTextAllowed(value)) { integers = value; }
RaisePropertyChanged("Number");
Hi I am beginner using C# trying to produce a WPF(MVVM).
I have currently a TextBox & a ComboBox on a Window Form.
At the moment, I would like to arrange such that when user input an Access DB file path into the TextBox, the ComboBox will be automatically updated such that its available Items is the Tables Name in the MDB file. When user changed the MDB file path to another, ComboBox Items will be refreshed as well.
I have already prepared below Properties in the GUI's ViewModel.
...
public string MdbDir { get{;} set {; RaisePropertyChanged("MdbDir");} }
public List<string> MdbTblList { get{;} set{...; RaisePropertyChanged("MdbTblList");}}
...
I have already prepared below method in the Model.
...
public List<string> ReturnMdbTblList(string mdbDir)
{
List<string> mdbTblList = new List<string>();
oCat = new ADOX.Catalog();
oCat.ActiveConnection = oConn;
foreach (ADOX.Table oTable in oCat.Tables)
{
mdbTblList.Add(oTable.Name);
}
return mdbTblList;
}
...
I have already prepared below in View.xaml
...
<TextBox Grid.Column="1" Grid.ColumnSpan="2" Text="{Binding MdbDir}" />
<ComboBox Grid.Column="1" Grid.Row="3" SelectedItem="{Binding Path=SelectedMdbTbl,Mode=TwoWay}" ItemsSource="{Binding MdbTblList}"/>
...
All I don't know is how to link the Model Method to ViewModel, and to make the ComboBox aware of MdbDir changed.
Any idea on what else to add the coding and at the same time minimize the amendment on the current piece of coding?
Thanks very much in advance :)
You can do that in two ways.
When ever you type the path in textBox and press tab, the Set part of the property MdbDir will be called. So you can call method like below. And in that method method you can fetch the details from the Model and update it to the UI.
public string MdbDir
{
get
{
;
} set
{
;
RaisePropertyChanged("MdbDir");
UpDateTheList()
}
}
Or you can have button on the UI and click of that can do the same thing. to Bind commands to buttons you can refer the below links
http://theprofessionalspoint.blogspot.in/2013/04/icommand-interface-and-relaycommand.html
http://www.codeproject.com/Articles/126249/MVVM-Pattern-in-WPF-A-Simple-Tutorial-for-Absolute
One more observation, if your new creating list every time then List is fine, but if your adding or removing something with already existing list then it'll not work for you, you have to use observablecollection instead of list
It is acceptable for your ViewModel to maintain a reference to your Model as the ViewModel can be thought of as a wrapper for your Model.
You could put a call to your Model method ReturnMdbTblList such as:
public string MdbDir
{
get
{
return this.mdbDir;
}
set
{
this.mdbDir = value;
RaisePropertyChanged("MdbDir");
this.MdbTblList = this.model.ReturnMdbTblList(value);
}
}
This is straight forward to implement and effective. My personal preference is not put anything inside the get and set methods of properties that do not directly affect the field it is accessing or notifying others it has changed. That is just my preference though, others may be happy to do so and I am not saying it is wrong.
I would use a DelegateCommand on the button to make the call to your ReturnMdbTdlList:
Model, ViewMode & DelegateCommand
public class MyViewModel : INotifyPropertyChanged
{
private readonly MyModel model;
private string mdbDir;
public string MdbDir
{
get
{
return this.mdbDir;
}
set
{
this.mdbDir = value;
RaisePropertyChanged("MdbDir");
}
}
private List<string> mdbTblList;
public List<string> MdbTblList
{
get
{
return this.mdbTblList;
}
set
{
this.mdbTblList = value;
RaisePropertyChanged("MdbTblList");
}
}
private DelegateCommand updateMdbTblListCommand;
public ICommand UpdateMdbTblListCommand
{
get
{
return this.updateMdbTblListCommand ??
(this.updateMdbTblListCommand = new DelegateCommand(this.UpdateMdbTblList));
}
}
public MyViewModel()
{
// This would idealy be injected via the constructor
this.model = new MyModel();
}
private void UpdateMdbTblList(object obj)
{
var param = obj as string;
this.MdbTblList = this.model.ReturnMdbTblList(param);
}
#region [ INotifyPropertyChanged ]
public event PropertyChangedEventHandler PropertyChanged;
[NotifyPropertyChangedInvocator]
protected virtual void RaisePropertyChanged([CallerMemberName] string propertyName = null)
{
var handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
#endregion
}
public class MyModel
{
public List<string> ReturnMdbTblList(string mdbDir)
{
// Do soemthing
return new List<string>();
}
}
public class DelegateCommand : ICommand
{
private readonly Predicate<object> _canExecute;
private readonly Action<object> _execute;
public event EventHandler CanExecuteChanged;
public DelegateCommand(Action<object> execute)
: this(execute, null)
{
}
public DelegateCommand(Action<object> execute,
Predicate<object> canExecute)
{
_execute = execute;
_canExecute = canExecute;
}
public bool CanExecute(object parameter)
{
return this._canExecute == null || this._canExecute(parameter);
}
public void Execute(object parameter)
{
_execute(parameter);
}
public void RaiseCanExecuteChanged()
{
if (CanExecuteChanged != null)
{
CanExecuteChanged(this, EventArgs.Empty);
}
}
}
XAML
<StackPanel Orientation="Horizontal" VerticalAlignment="Top">
<TextBox Height="23" Margin="10" Width="200" Text="{Binding MdbDir}" />
<Button Content="Click Me" Width="100" Height="25" Margin="10" Command="{Binding Path=UpdateMdbTblListCommand}" CommandParameter="{Binding Path=MdbDir}" />
</StackPanel>
We bind the Command property of the Button to our UpdateMdbTblCommand in the MyViewModel, we also bind the CommandParameter property of the Button to the MdbDir property of MyViewModel. When the Button is pressed the UpdateMdbTblCommand is executed which in turn calls the UpdateMdbTbl passing along the value of MdbDir as an argument and subsequently updating the MdbTblList property of MyViewModel.
As I said the DelegateCommand would be my preferred method, however, it may be overkill when taking into consideration what you have to write to achieve what can be done in the former example.
I'm new to WPF, so there's probably something basic I'm missing here. I have an application that looks like this:
<Window x:Class="MyApp.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Test Application" Height="647" Width="723" Background="#88B0FF">
<DockPanel Name="MainDock">
<Button DockPanel.Dock="Top" Margin="5,0,5,0" x:Name="PingButton" Click="PingButton_OnClick">Ping</Button>
<TextBox Text="{Binding Path=Output}" />
</DockPanel>
</Window>
The code-behind is like this:
public partial class MainWindow : Window
{
private Model _applicationModel = new Model();
public Model ApplicationModel {
get { return _applicationModel; }
set { _applicationModel = value; }
}
public MainWindow()
{
InitializeComponent();
this.DataContext = ApplicationModel;
ApplicationModel.Output = "Not clicked";
}
private void PingButton_OnClick(object sender, RoutedEventArgs e)
{
ApplicationModel.Output = "Clicked";
}
}
I have a small class called Model that implements INotifyPropertyChanged.
public class Model : INotifyPropertyChanged
{
public string Output { get; set; }
public event PropertyChangedEventHandler PropertyChanged;
[NotifyPropertyChangedInvocator]
protected virtual void OnPropertyChanged(string propertyName)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
}
}
I run this application, and the text box displays the text "Not clicked". When I click the button, I would expect that the text would change. It does not. The "ApplicationModel" object is updated, and this is reflected in the DataContext; I have a breakpoint in the OnPropertyChanged() method, however, and it appears that it's never being called.
What am I doing wrong?
OnPropertyChanged() isn't being called because you're not calling it.
There is no special magic that wires up calls to OnPropertyChanged by itself, so you need to do it yourself.
Specifically, you should modify your Output property to call it when it changes (and it wouldn't hurt to do the same for your ApplicationModel property:
private string output;
public string Output
{
get { return output; }
set
{
if (output != value)
{
output = value;
OnPropertyChanged("Output");
}
}
}
If you're targeting .NET 4.5 you can utilize the CallerMemberName attribute to reduce boilerplate code; This article explains how to do so. Then you'll have something like this:
private string output;
public string Output
{
get { return output; }
set { SetProperty(ref output, value); }
}
If you're using .NET 4.0 or below, you can use expression trees, as described in this answer.
Title pretty much says it all. The score is being displayed as 0 (which is what I initialized it to). However, when updating the Score it's not propagating to the UI textBlock. Thought this would be pretty simple, but I'm always running into problems making the switch from Android :) Am I suppose to be running something on the UI thread??
I'm trying to bind to the "Score" property.
<TextBox x:Name="text_Score" Text="{Binding Score, Mode=OneWay}" HorizontalAlignment="Left" Margin="91,333,0,0" Grid.Row="1" TextWrapping="Wrap" VerticalAlignment="Top" Height="148" Width="155" FontSize="72"/>
Here is my holder class
public class GameInfo
{
public int Score { get; set; }
public int counter = 0;
}
**Note: Make sure you don't forget to add {get; set;} or else nothing will show up.
and this is where I'm trying to set it
protected override void OnNavigatedTo(NavigationEventArgs e)
{
base.OnNavigatedTo(e);
info.counter = (int)e.Parameter;
text_Score.DataContext = info;
}
P.S. To reiterate, I'm going for OneWay. I only want to display the score and have it undated when the variable changes. I plan on disabling user input.
Here is the full working code example. The only thing that had to change was my holder class. Thanks Walt.
public class GameInfo : INotifyPropertyChanged
{
private int score;
public int Score {
get { return score; }
set
{
if (Score == value) return;
score = value;
NotifyPropertyChanged("Score");
}
}
public int counter = 0;
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged([CallerMemberName] String propertyName = "")
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
In XAML binding your underlying class needs to inform the binding framework that the value has changed. I your example, you are setting the counter in the OnNavigatedTo event handler. But if you look at your GameInfo class, it's a simple data object.
The INotifyPropertyChanged interface is used to notify clients, typically binding clients, that a property value has changed. So in your case, change the class as follows
public class GameInfo : INotifyPropertyChanged
{
private int _score;
public int Score
{
get
{
return this._score;
}
set
{
if (value != this._score)
{
this._score = value;
NotifyPropertyChanged("Score");
}
}
}
public int counter = 0; // if you use _score, then you don't need this variable.
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged([CallerMemberName] String propertyName = "")
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
See the MSDN article for more information INotifyPropertyChanged