In a WPF form .NET framework, I'm trying to achieve the following (seemingly) simple task:
I have 3 buttons and 3 textboxes:
Button 1
Textbox1
Button 2
Textbox2
Button 3
Textbox3
If I click button 1, I want textbox 1 to read true and the other 2 false. If I click button 2, I want textbox 2 to show true and the others false and the same for button 3 and textbox 3 respectively.
I thought I could achieve this by setting the value of all of the Booleans to either true or false depending on the button that has been clicked using the click event, but don't get the expected result
using System;
using System.Windows;
using System.Threading;
using System.Windows.Threading;
namespace WPF_Test
{
public partial class MainWindow : Window
{
bool value1;
bool value2;
bool value3;
public MainWindow()
{
InitializeComponent();
if (value1 == true)
{
textbox1.Text = value1.ToString();
} else if (value2 == true){
textbox2.Text = value2.ToString();
} else if (value3 == true){
textbox3.Text = value3.ToString();
}
}
private void button1_Click(object sender, RoutedEventArgs e)
{
value1 = true;
value2 = false;
value3 = false;
}
private void button2_Click(object sender, RoutedEventArgs e)
{
value1 = false;
value2 = true;
value3 = false;
}
private void button3_Click(object sender, RoutedEventArgs e)
{
value1 = false;
value2 = false;
value3 = true;
}
}
}
Any idea what I might be missing?
referencing Rekshino's comment:
Just move the code in the constructor after InitializeComponent to the separate method and then call it at the bottom each Button.Click event handler. – Rekshino
however, my limited knowledge does not certify that this is the 'best' version so for this I refer to D M's answer as well
In order to update the UI in WPF by binding to a value in the code behind, you need to change the fields to properties and implement the INotifyPropertyChanged interface. There's a really good answer on how to do this by Marc Gravell.
public partial class MainWindow : Window, INotifyPropertyChanged
{
private bool _value1;
public bool value1
{
get => _value1;
set => SetField(ref _value1, value);
}
private void button1_Click(object sender, RoutedEventArgs e)
{
value1 = true;
}
// Implementation of INotifyPropertyChanged with a
// SetField helper method to ensure you're only
// notifying when a value actually changes.
public event PropertyChangedEventHandler? PropertyChanged;
protected void OnPropertyChanged(string? propertyName)
=> PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
protected bool SetField<T>(ref T field, T value, [CallerMemberName] string? propertyName = null)
{
if (EqualityComparer<T>.Default.Equals(field, value)) return false;
field = value;
OnPropertyChanged(propertyName);
return true;
}
}
Related
I'm making a custom behavior for Telerik's RadGridView.
When this behavior is attached and its PropertyName is set to same property as specified by
DataMemberBinding value of some of the GridViewCheckBoxColumn of the grid, then toggling the checkbox in that column will apply same checkbox state to all selected rows (but only to the same column).
That happens in the ApplyToAllSelected method, namely in gvcb.SetCurrentValue(GridViewCheckBox.IsCheckedProperty, isChecked); line. The visuals are working as expected, and all checkbox values are updated on screen.
The Problem is that the binding source is not updated for those rows. Only for the one where click happened. GridViewCheckBox.IsChecked dependency property does not seem to be bound directly to the datacontext's property, so gvcb.GetBindingExpression(GridViewCheckBox.IsChecked) returns null.
The Question: how to update source after setting checkbox state?
public sealed class CheckAllSelectedBehavior : Behavior<RadGridView>
{
public event EventHandler Toggled;
public string PropertyName { get; set; }
protected override void OnAttached()
{
base.OnAttached();
this.AssociatedObject.PreparingCellForEdit += this.AssociatedObject_PreparedCellForEdit;
this.AssociatedObject.CellEditEnded += this.AssociatedObject_CellEditEnded;
}
protected override void OnDetaching()
{
this.AssociatedObject.PreparingCellForEdit -= this.AssociatedObject_PreparedCellForEdit;
this.AssociatedObject.CellEditEnded -= this.AssociatedObject_CellEditEnded;
base.OnDetaching();
}
private void AssociatedObject_CellEditEnded(object sender, GridViewCellEditEndedEventArgs e)
{
if (e.Cell.Column.UniqueName == this.PropertyName && e.EditingElement is CheckBox cb)
{
cb.Checked -= this.Cb_Checked;
cb.Unchecked -= this.Cb_Unchecked;
}
}
private void AssociatedObject_PreparedCellForEdit(object sender, GridViewPreparingCellForEditEventArgs e)
{
if (e.Column.UniqueName == this.PropertyName && e.EditingElement is CheckBox cb)
{
cb.Checked += this.Cb_Checked;
cb.Unchecked += this.Cb_Unchecked;
}
}
private void Cb_Unchecked(object sender, System.Windows.RoutedEventArgs e)
{
this.ApplyToAllSelected(false);
}
private void Cb_Checked(object sender, System.Windows.RoutedEventArgs e)
{
this.ApplyToAllSelected(true);
}
private void ApplyToAllSelected(bool isChecked)
{
foreach (var item in this.AssociatedObject.SelectedItems)
{
var row = this.AssociatedObject.GetRowForItem(item);
var cell = row.GetCellFromPropertyName(this.PropertyName);
if (cell.Content is GridViewCheckBox gvcb)
{
gvcb.SetCurrentValue(GridViewCheckBox.IsCheckedProperty, isChecked);
}
}
this.Toggled?.Invoke(this, EventArgs.Empty);
}
}
Using reflection to set the value on viewmodel property seems to work. Modify the ApplyToAllSelected method like follows:
private void ApplyToAllSelected(bool isChecked)
{
foreach (var item in this.AssociatedObject.SelectedItems)
{
this.SetProperty(item, isChecked);
}
this.Toggled?.Invoke(this, EventArgs.Empty);
}
private void SetProperty(object target, bool isChecked)
{
var prop = target.GetType().GetProperty(
this.PropertyName,
BindingFlags.Instance | BindingFlags.Public | BindingFlags.SetProperty);
prop.SetValue(target, isChecked);
}
I have a custom UserControl subclassing from RichTextBox. This class has a dependency property, Equation, that is bound two-way.
When the user drops an item onto the control I change Equation. This properly propagates the change to the other end of the binding, which triggers a property changed notification, but the UI is not changing. If I change the binding to a different object and back it then displays the updated Equation.
How can I force the refresh without changing the binding? Right now I'm setting Equation=null and then back which works, but that seems hackish. There must be something more elegant.
Here are relevant portions of the control. What I would like to happen is for the OnEquationChanged callback to be called after I change Equation (Equation.Components.Add(txt)).
public class EquationTextBox : RichTextBox
{
protected override void OnDrop(DragEventArgs e)
{
if (e.Data.GetDataPresent(DataFormats.StringFormat))
{
string str = (string)e.Data.GetData(DataFormats.StringFormat);
EquationText txt = new EquationText(str);
//// Preferred /////
Equation.Components.Add(txt);
//// HACK /////
Equation eqn = this.Equation;
eqn.Components.Add(txt);
this.Equation = null;
this.Equation = eqn;
///////////////
Console.WriteLine("Dropping " + str);
}
}
public Equation Equation
{
get { return (Equation)GetValue(EquationProperty); }
set { SetValue(EquationProperty, value); }
}
private static void onEquationChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
string prop = e.Property.ToString();
EquationTextBox txtBox = d as EquationTextBox;
if(txtBox == null || txtBox.Equation == null)
return;
FlowDocument doc = txtBox.Document;
doc.Blocks.Clear();
doc.Blocks.Add(new Paragraph(new Run(txtBox.Equation.ToString())));
}
public static readonly DependencyProperty EquationProperty =
DependencyProperty.Register("Equation",
typeof(Equation),
typeof(EquationTextBox),
new FrameworkPropertyMetadata(null,
FrameworkPropertyMetadataOptions.AffectsRender,
new PropertyChangedCallback(onEquationChanged)));
private bool mIsTextChanged;
}
}
Here is the property on the other end of the two-way binding. The equation_PropertyChanged event is getting called in the above code as a result of Equation.Components.Add(txt);
public Equation Equation
{
get{ return mEquation; }
set { mEquation = value; NotifyPropertyChanged(); }
}
private void equation_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
NotifyPropertyChanged("Equation");
}
private void NotifyPropertyChanged([CallerMemberName] String propertyName = "")
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
Edit --------------------------
Per the comments, I tried using a dispatcher like this (note that this is my first attempt at using a dispatcher)
string str = (string)e.Data.GetData(DataFormats.StringFormat);
EquationText txt = new EquationText(str);
Application.Current.Dispatcher.BeginInvoke(DispatcherPriority.Normal, new Action(() =>
{
Equation.Components.Add(txt);
NotifyPropertyChanged("Equation");
}));
but still no UI update.
Edit 2 --------------------------
The 2-way binding is done in XAML
<l:EquationTextBox x:Name="ui_txtVariableEquation" Grid.Row="0" Grid.Column="2"
Grid.RowSpan="3" HorizontalAlignment="Stretch" VerticalAlignment="Stretch"
AllowDrop="True"
Equation="{Binding SelectedVariableVM.Variable.Equation, Mode=TwoWay}">
</l:EquationTextBox>
Info relevant to the Components object (with in the Equation class)
public class Equation : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public Equation()
{
mComponents = new ObservableCollection<EquationComponent>();
mComponents.CollectionChanged += new NotifyCollectionChangedEventHandler(components_CollectionChanged);
}
public Equation(string eqn) : this()
{
mComponents.Add(new EquationText(eqn));
}
public ObservableCollection<EquationComponent> Components
{
get{ return mComponents; }
set{ mComponents = value; NotifyPropertyChanged();}
}
public override string ToString()
{
string str = "";
for(int i=0; i<mComponents.Count; i++)
str += mComponents[i].ToString();
return str;
}
private void components_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
NotifyPropertyChanged("Components");
}
private ObservableCollection<EquationComponent> mComponents;
private void NotifyPropertyChanged([CallerMemberName] String propertyName = "")
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
public class Variable : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public Variable(string name = "var", VariableType type = VariableType.UnknownType) :
this(name, "", 0, type)
{
}
and ...
public class Variable : INotifyPropertyChanged
{
public Variable(string name, string unit, object val, VariableType type)
{
mEquation = new Equation(name + " = " + val.ToString() +
mEquation.PropertyChanged += new PropertyChangedEventHandler(equation_PropertyChanged);
}
...
public Equation Equation
{
get{ return mEquation; }
set { mEquation = value; NotifyPropertyChanged(); }
}
private void equation_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
NotifyPropertyChanged("Equation");
}
private void NotifyPropertyChanged([CallerMemberName] String propertyName = "")
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
private Equation mEquation;
...
}
Variable.equation_PropertyChanged is called when the event is raised inside of the Equation class
I think the problem is that the value produced by the binding is not actually changing (it's still the same Equation object). If the DP value doesn't change, then your DP change handler will not be called.
Perhaps, in your DP change handler, you should subscribe to the new equation's PropertyChanged event and then rebuild your document when an underlying property changes:
private static void onEquationChanged(
DependencyObject d,
DependencyPropertyChangedEventArgs e)
{
var txtBox = d as EquationTextBox;
if (txtBox == null)
return;
var oldEquation = e.OldValue as Equation;
if (oldEquation != null)
oldEquation.PropertyChanged -= txtBox.OnEquationPropertyChanged;
var newEquation = e.NewValue as Equation;
if (newEquation != null)
newEquation.PropertyChanged += txtBox.OnEquationPropertyChanged;
txtBox.RebuildDocument();
}
private void OnEquationPropertyChanged(object sender, EventArgs e)
{
RebuildDocument();
}
private void RebuildDocument()
{
FlowDocument doc = this.Document;
doc.Blocks.Clear();
var equation = this.Equation;
if (equation != null)
doc.Blocks.Add(new Paragraph(new Run(equation.ToString())));
}
I have 6 Textbox in a registration form with some preset text.
When I click in the Textbox for Name then preset text Enter your full name should disappear...If I then click on the Textbox for Email without writing anything in Name Textbox, then Enter your full name should appear again. This event should happen for all my Textbox, but their should be different text in each Textbox...Not Enter your full name in all of them. Can someone please help me with this?
The code I have right now makes it possible to clear the Textbox as I click on them, using GotFocus event.
private void textBox1_GotFocus(Object sender, EventArgs e)
{
textBox1.Clear();
}
In the textboxes, I have preset text....I want that exact preset text for each textbox to come back whenever I am clicking outside that Textbox.
I've heard something about a "Placeholder" ?
Here's how it looks now with an extra constructor. I can't figure out what I'm doing wrong?
public partial class CustomTextbox : TextBox
{
private const string _text = #"Enter your full name";
private bool _isEmpty = true;
public CustomTextbox()
{
base.ForeColor = SystemColors.GrayText;
Text = _text;
Leave += LeaveTextBox;
Enter += EnterTextBox;
TextChanged += TextChangedTextBox;
}
public CustomTextbox(string tempText)
{
base.ForeColor = SystemColors.GrayText;
Text = tempText;
Leave += LeaveTextBox;
Enter += EnterTextBox;
TextChanged += TextChangedTextBox;
}
Try creating a custom Class inheriting from Textbox. To achieve this, create a new Usercontrol. Delete the base class name and place Textbox there. Delete anything in designer if giving compilation error. Build your project. You should see a new control in toolbox. Use it and you are good to go. Here is the code for Usercontol.cs
public partial class CustomTextbox : TextBox
{
private const string _text = #"Enter your full name";
private bool _isEmpty = true;
public CustomTextbox()
{
InitializeComponent();
base.ForeColor = SystemColors.GrayText;
Text = _text;
Leave += LeaveTextBox;
Enter += EnterTextBox;
TextChanged += TextChangedTextBox;
}
private void TextChangedTextBox(object sender, EventArgs e)
{
_isEmpty = string.IsNullOrEmpty(Text);
}
public override sealed string Text
{
set
{
base.Text = value;
}
}
private void LeaveTextBox(object sender, EventArgs e)
{
if (string.IsNullOrEmpty(Text))
{
Text = _text;
_isEmpty = true;
base.ForeColor = SystemColors.GrayText;
}
else
{
_isEmpty = false;
}
}
private void EnterTextBox(object sender, EventArgs e)
{
if (_isEmpty)
Text = string.Empty;
base.ForeColor = SystemColors.ControlText;
}
}
Let me know if you need any further information. As a not, you can also make _text as a public property and use it to set the desired text property.
Hope it helps.
I solved my problem!
Thanks to #rapsalands for providing the code.
I have now modified it for my needs.
public partial class UserControl1 : UserControl
{
public UserControl1()
{
InitializeComponent();
}
}
public partial class CustomTextbox : TextBox
{
private string defaultText;
public string DefaultText
{
get { return defaultText; }
set { defaultText = value; }
}
private bool _isEmpty = true;
public CustomTextbox(string myText)
{
base.ForeColor = SystemColors.GrayText;
this.Text = myText;
this.DefaultText = myText;
Leave += LeaveTextBox;
Enter += EnterTextBox;
TextChanged += TextChangedTextBox;
}
private void TextChangedTextBox(object sender, EventArgs e)
{
_isEmpty = string.IsNullOrEmpty(Text);
}
public override sealed string Text
{
set
{
base.Text = value;
}
}
private void LeaveTextBox(object sender, EventArgs e)
{
if (string.IsNullOrEmpty(Text))
{
Text = defaultText;
_isEmpty = true;
base.ForeColor = SystemColors.GrayText;
}
else
{
_isEmpty = false;
}
}
private void EnterTextBox(object sender, EventArgs e)
{
if (_isEmpty)
Text = string.Empty;
base.ForeColor = SystemColors.ControlText;
}
}
}
I need a feature in silverlight text box, simillar functionality as in Ask Question "Title" textbox in stalkoverflow. When there's no text in textbox then it should display "Search". When user clicked on the textbox then textbox text should be empty and in textbox lost focus if the text is empty then show "Search". I wrote the following code, but is there any code which handles all possible conditions?
private void txtAvailable_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
txtAvailable.Text = "";
}
private void txtAvailable_LostFocus(object sender, RoutedEventArgs e)
{
if (txtAvailable.Text.Trim() == "")
txtAvailable.Text = "Search";
}
You could use the Textbox GotFocus and LostFocus events - they should be generic enough to cover off all of your potentials..
The specialness comes when you want to search on every keystroke - you have to enable and disable searching on those events.
private bool IsBusy
{
get;
set;
}
private bool CanSearch
{
get;
set;
}
public Constructor()
{
InitializeComponent();
this.IsBusy = false;
txtSearch.GotFocus += new RoutedEventHandler( txtSearch_GotFocus );
txtSearch.LostFocus += new RoutedEventHandler( txtSearch_LostFocus );
txtSearch.KeyUp += new System.Windows.Input.KeyEventHandler( txtSearch_KeyUp );
txtSearch.Text = "Search »";
}
private void txtSearch_LostFocus( object sender, RoutedEventArgs e )
{
if( string.IsNullOrEmpty( txtSearch.Text ) )
{
CanSearch = false;
txtSearch.Text = "Search »";
}
}
private void txtSearch_GotFocus( object sender, RoutedEventArgs e )
{
txtSearch.Text = string.Empty;
CanSearch = true;
}
private void OnFilterCommand()
{
try
{
if( !IsBusy && CanSearch )
{
AppMessages.FilterAssetMessage.Send( txtSearch.Text );
}
}
catch( Exception ex )
{
// Notify user if there is any error
AppMessages.RaiseErrorMessage.Send( ex );
}
}
private void txtSearch_KeyUp( object sender, System.Windows.Input.KeyEventArgs e )
{
OnFilterCommand();
}
If you're familiar with WPF and the differences between WPF and Silverlight, take a look at the WatermarkTextBox in the extended WPF toolkit:
http://wpftoolkit.codeplex.com/wikipage?title=WatermarkTextBox&referringTitle=Home
The source is available, so you could try porting that control to Silverlight.
OK - So I almost have this working. I just need to know who to get the usercontrol to let the viewmodel of the consuming view know there has been a change. Check this out - here is xaml from the consuming view.
<StackPanel>
<pfControls:TriChoiceUserControl Text="{Binding Path=SampleText}" State="{Binding CurrentState}"/>
</StackPanel>
Here is the viewmodel code
class MainWindowViewModel: INotifyPropertyChanged
{
private bool? currentState;
public bool? CurrentState
{
get { return currentState; }
set {
currentState = value;
OnPropertyChanged("CurrentState");
}
}
public string SampleText { get { return "Hi there"; } }
public MainWindowViewModel()
{
CurrentState = false;
}
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string name)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(name));
}
}
}
Now on the initial load of the ViewModel you can see that Current state is false and indeed the control I ends up with the false check box checked (there are three check boxes, one for yes, one for no and one for na - don't ask me, that is what they told me to do). Problem is that when I check the first one (true in this case) the user control is working in that it goes and unchecks the false check box but and changes the state property but my viewmodel for the consuming view never gets notified. I feel like I am so close... Here is the code for the user control.
public partial class TriChoiceUserControl : UserControl, INotifyPropertyChanged
{
#region Fields (5)
public static readonly DependencyProperty StateProperty = DependencyProperty.Register("State", typeof(bool?), typeof(TriChoiceUserControl),
new FrameworkPropertyMetadata(new PropertyChangedCallback(ChangeState)));
public static readonly DependencyProperty TextProperty = DependencyProperty.Register("Text", typeof(String), typeof(TriChoiceUserControl),
new FrameworkPropertyMetadata(new PropertyChangedCallback(ChangeText)));
#endregion Fields
public TriChoiceUserControl()
{
InitializeComponent();
}
public bool? State
{
get
{
return (bool?)GetValue(StateProperty);
}
set
{
SetValue(StateProperty, value);
NotifyPropertyChanged("State");
}
}
public string Text
{
get
{
return (string)GetValue(TextProperty);
}
set
{
SetValue(TextProperty, value);
}
}
private static void ChangeState(DependencyObject source, DependencyPropertyChangedEventArgs e)
{
(source as TriChoiceUserControl).UpdateCheckState((bool?)e.NewValue);
}
private static void ChangeText(DependencyObject source, DependencyPropertyChangedEventArgs e)
{
(source as TriChoiceUserControl).UpdateText(e.NewValue.ToString());
}
private void UpdateText(string newText)
{
label1.Content = newText;
}
private void UpdateCheckState(bool? newState)
{
if (newState != null)
{
if ((bool)newState)
{
chkYes.IsChecked = true;
chkNo.IsChecked = false;
chkNa.IsChecked = false;
}
else
{
chkYes.IsChecked = false;
chkNo.IsChecked = true;
chkNa.IsChecked = false;
}
}
else
{
chkYes.IsChecked = false;
chkNo.IsChecked = false;
chkNa.IsChecked = true;
}
State = newState;
}
private void chkYes_Checked(object sender, RoutedEventArgs e)
{
UpdateCheckState(true);
}
private void chkNo_Checked(object sender, RoutedEventArgs e)
{
UpdateCheckState(false);
}
private void chkNa_Checked(object sender, RoutedEventArgs e)
{
UpdateCheckState(null);
}
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(String info)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(info));
}
}
}
Here is the XAML for the user control.
Thanks for any input.
All of this works just fine, I had lost sight of the fact that the default mode is "oneWay" on the binding - duh - I set Mode=TwoWay and no everything works. But that OK, I don't mind saying duh, it usually means I have found the answer :)