public string TenNguoiDung
{
get { return _currentNguoiDung.TenNguoiDung; }
set
{
_currentNguoiDung.TenNguoiDung = value != null
? value.ToStandardString(true) : string.Empty;
SendPropertyChanged("TenNguoiDung");
ValidProperty(_currentNguoiDung.TenNguoiDung, new
ValidationContext(_currentNguoiDung) { MemberName = "TenNguoiDung" });
}
}
public static string ToStandardString(this string value,
bool isAllStartWithUpper = false)
{
string result = string.Empty;
value = value.Trim();
var listWord = value.Split(' ').ToList();
listWord.RemoveAll(p => p == string.Empty);
foreach (var item in listWord) result +=
item.Substring(0, 1).ToUpper() + item.Substring(1).ToLower() + " ";
if (!isAllStartWithUpper) result =
result.Substring(0, 1) + result.Substring(1).ToLower();
return result.Trim();
}
I have a TextBox:
<TextBox Grid.Column="1" Margin="1" Text=
"{Binding Path=TenNguoiDung,Mode=TwoWay,NotifyOnValidationError=True}"/>
When i typed some clear text to Standard, Setter called SendPropertyChanged("TenNguoiDung") but on UI not update. How can i fix that??
Edit:
public void SendPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
Edit:
I debuged at Setter, i saw _currentNguoiDung.TenNguoiDung changed after ToStandardString, but on UI was not updated
Update
I used few hours to google and i got my answer.
I feel happy when this bug only happen on debug mode. When i run application without debug, it work perfect!!
Fix debug Mode:
[link]Coerce Value in Property Setter - Silverlight 5
Right click on the Web project in Solution Explorer and select
Properties.
Select the Web tab.
Scroll down to the Debuggers section.
Uncheck the checkbox labelled Silverlight.
Make sure you are setting the data context to the instance of the class containing your property.
From the looks of it you are doing this but make sure you are implementing INotifyPropertyChanged
When debugging if you have any binding errors happening these will show in the output window.
Is the value changing if you type in 'hello' and then replace it with 'world'? If so it may just be a case sensitivity issue
Related
I'm building WPF application that's using the Smith HTML Editor. The application is MVVM and I've got the Smith control bound to a field with HTML content. Everything is working ok, except that the IsDirty implementation that I'm working on gets triggered if you click in or even mouse over the HTML control. I looked a little closer and found that as soon as the control looses focus it changes all my tags to upper case (thus the app sees the field as changed and sets the model to dirty.)
I'm having a hard time putting my hands on any documentation for this control, but so far I haven't been able to find anywhere to control this behaviour.
Here's the XAML for the control:
<smith:HtmlEditor Name="fldcomments" Height="320" BindingContent="{Binding Path=Student.Comments, Mode=TwoWay }"/>
I'd appreciate any hints.
So this is kludgey as hell, but it's the best I came up with. I used NUglify to extract the plain text and added that as a comparison for my field setter function. Here's the code for the function:
protected void SetField<T>(ref T field, T value, string propertyName)
{
if (!EqualityComparer<T>.Default.Equals(field, value))
{
IsDirty = true;
if ((propertyName == "Comments") && (value != null) && (field != null))
{
var ugField = Uglify.HtmlToText((value as string));
var ugValue = Uglify.HtmlToText(field as string);
string strField = Regex.Replace(ugField.Code, #"\s+", string.Empty);
string strValue = Regex.Replace(ugValue.Code, #"\s+", string.Empty);
if (strField == strValue)
{ IsDirty = false; }
}
field = value;
OnPropertyChanged(propertyName);
}
}
I had to add the regex.replace to get rid of some extra whitespace that the HTMLToText function left in there.
I'm not proud, but it works. Still very open to a better solution.
I noticed this while trying to set binding for a short period of time in code. In fact, I just want to get value provided by binding. So I set the binding, get value of the target property and immediately clear the binding. Everything is good until the RelativeSource with mode FindAncestor is set for the binding. In this case the target property returns its default value.
After some debugging I discovered that the BindingExpression for the FindAncestor binding has its property Status set to Unattached. For other types of bindings BindingExpression.Status is set to Active.
I've written some code to illustrate this.
Window1.xaml
<Window x:Class="Wpf_SetBindingInCode.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
x:Name="Window"
Title="Window1"
Height="300" Width="300"
DataContext="DataContext content">
<StackPanel>
<Button Content="Set binding" Click="SetBindingButtonClick"/>
<TextBlock x:Name="TextBlock1"/>
<TextBlock x:Name="TextBlock2"/>
<TextBlock x:Name="TextBlock3"/>
</StackPanel>
</Window>
Window1.xaml.cs
public partial class Window1 : Window
{
public Window1()
{
InitializeComponent();
}
private void SetBindingButtonClick(object sender, RoutedEventArgs e)
{
Binding bindingToRelativeSource = new Binding("DataContext")
{
RelativeSource = new RelativeSource { Mode = RelativeSourceMode.FindAncestor, AncestorType = typeof(Window1) },
};
Binding bindingToElement = new Binding("DataContext")
{
ElementName = "Window"
};
Binding bindingToDataContext = new Binding();
BindingOperations.SetBinding(TextBlock1, TextBlock.TextProperty, bindingToRelativeSource);
BindingOperations.SetBinding(TextBlock2, TextBlock.TextProperty, bindingToElement);
BindingOperations.SetBinding(TextBlock3, TextBlock.TextProperty, bindingToDataContext);
Trace.WriteLine("TextBlock1.Text = \"" + TextBlock1.Text + "\"");
Trace.WriteLine("TextBlock2.Text = \"" + TextBlock2.Text + "\"");
Trace.WriteLine("TextBlock3.Text = \"" + TextBlock3.Text + "\"");
var bindingExpressionBase1 = BindingOperations.GetBindingExpressionBase(TextBlock1, TextBlock.TextProperty);
var bindingExpressionBase2 = BindingOperations.GetBindingExpressionBase(TextBlock2, TextBlock.TextProperty);
var bindingExpressionBase3 = BindingOperations.GetBindingExpressionBase(TextBlock3, TextBlock.TextProperty);
Trace.WriteLine("bindingExpressionBase1.Status = " + bindingExpressionBase1.Status);
Trace.WriteLine("bindingExpressionBase2.Status = " + bindingExpressionBase2.Status);
Trace.WriteLine("bindingExpressionBase3.Status = " + bindingExpressionBase3.Status);
}
}
The code above produces the following output:
TextBlock1.Text = ""
TextBlock2.Text = "DataContext content"
TextBlock3.Text = "DataContext content"
bindingExpressionBase1.Status = Unattached
bindingExpressionBase2.Status = Active
bindingExpressionBase3.Status = Active
But despite this all three TextBlocks on the form has expected values - "DataContext content".
So my questions are:
Why the RelativeSourceMode.FindAncestor binding does not provide the
value immediately after BindingOperations.SetBinding(...) is called?
Is there any way to force this kind of binding to update the target
property? I tried to call bindingExpression.UpdateTarget() - it
doesn't work like expected.
It's by design. To understand why, let's look into the code.
When an Expression is set as a value of a DependencyProperty the Expression.OnAttach is called (source). This method is overriden in the BindingExpressionBase class (source):
internal sealed override void OnAttach(DependencyObject d, DependencyProperty dp)
{
if (d == null)
throw new ArgumentNullException("d");
if (dp == null)
throw new ArgumentNullException("dp");
Attach(d, dp);
}
internal void Attach(DependencyObject target, DependencyProperty dp)
{
// make sure we're on the right thread to access the target
if (target != null)
{
target.VerifyAccess();
}
IsAttaching = true;
AttachOverride(target, dp);
IsAttaching = false;
}
The AttachOverride method is virtual too and it's overriden in the BindingExpression (source).
internal override bool AttachOverride(DependencyObject target, DependencyProperty dp)
{
if (!base.AttachOverride(target, dp))
return false;
// listen for InheritanceContext change (if target is mentored)
if (ParentBinding.SourceReference == null || ParentBinding.SourceReference.UsesMentor)
{
DependencyObject mentor = Helper.FindMentor(target);
if (mentor != target)
{
InheritanceContextChangedEventManager.AddHandler(target, OnInheritanceContextChanged);
UsingMentor = true;
if (TraceData.IsExtendedTraceEnabled(this, TraceDataLevel.Attach))
{
TraceData.Trace(TraceEventType.Warning,
TraceData.UseMentor(
TraceData.Identify(this),
TraceData.Identify(mentor)));
}
}
}
// listen for lost focus
if (IsUpdateOnLostFocus)
{
Invariant.Assert(!IsInMultiBindingExpression, "Source BindingExpressions of a MultiBindingExpression should never be UpdateOnLostFocus.");
LostFocusEventManager.AddHandler(target, OnLostFocus);
}
// attach to things that need tree context. Do it synchronously
// if possible, otherwise post a task. This gives the parser et al.
// a chance to assemble the tree before we start walking it.
AttachToContext(AttachAttempt.First);
if (StatusInternal == BindingStatusInternal.Unattached)
{
Engine.AddTask(this, TaskOps.AttachToContext);
if (TraceData.IsExtendedTraceEnabled(this, TraceDataLevel.AttachToContext))
{
TraceData.Trace(TraceEventType.Warning,
TraceData.DeferAttachToContext(
TraceData.Identify(this)));
}
}
GC.KeepAlive(target); // keep target alive during activation (bug 956831)
return true;
}
In the listed code we can see that after all actions BindingExpression can be still Unattached. Let's see why it is so in our situation. For that we need to determine where the status is changed. This can be done by IL Spy which shows that the status is changed in the AttachToContext (source).
// try to get information from the tree context (parent, root, etc.)
// If everything succeeds, activate the binding.
// If anything fails in a way that might succeed after further layout,
// just return (with status == Unattached). The binding engine will try
// again later. For hard failures, set an error status; no more chances.
// During the "last chance" attempt, treat all failures as "hard".
void AttachToContext(AttachAttempt attempt)
{
// if the target has been GC'd, just give up
DependencyObject target = TargetElement;
if (target == null)
return; // status will be Detached
bool isExtendedTraceEnabled = TraceData.IsExtendedTraceEnabled(this, TraceDataLevel.AttachToContext);
bool traceObjectRef = TraceData.IsExtendedTraceEnabled(this, TraceDataLevel.SourceLookup);
// certain features should never be tried on the first attempt, as
// they certainly require at least one layout pass
if (attempt == AttachAttempt.First)
{
// relative source with ancestor lookup
ObjectRef or = ParentBinding.SourceReference;
if (or != null && or.TreeContextIsRequired(target))
{
if (isExtendedTraceEnabled)
{
TraceData.Trace(TraceEventType.Warning,
TraceData.SourceRequiresTreeContext(
TraceData.Identify(this),
or.Identify()));
}
return;
}
}
It is said in the comments that some features requires at least one layout pass and that one of them is RelativeSource with ancestor lookup (source).
internal bool TreeContextIsRequired(DependencyObject target)
{
return ProtectedTreeContextIsRequired(target);
}
/// <summary> true if the ObjectRef really needs the tree context </summary>
protected override bool ProtectedTreeContextIsRequired(DependencyObject target)
{
return ( (_relativeSource.Mode == RelativeSourceMode.FindAncestor
|| (_relativeSource.Mode == RelativeSourceMode.PreviousData)));
}
Because a tree context is required for the RelativeSource the BindingExpression is Unattached. Therefore the property value isn't updated immediately.
Invoke UpdateLayout on any UIElement to force layout updating and attaching BindingExpression's.
I've been working on an application in MVVM Light lately. I have a TextBox in my XAML bound to my UI. I'd like to validate any input and ensure that only numbers are entered. I've tried the following code:
My TextBox:
<TextBox TabIndex="1" Height="23" MinWidth="410" DockPanel.Dock="Left"
HorizontalAlignment="Left"
Text="{Binding Input, UpdateSourceTrigger=PropertyChanged}"
IsEnabled="{Binding IsEnabled}"
AcceptsReturn="False"
local:FocusExtension.IsFocused="{Binding IsFocused}">
And in my ViewModel:
private string input;
public string Input
{
get { return this.input; }
set
{
decimal test;
if(decimal.TryParse(value, out test))
{
this.input = value;
}
else
{
this.input = "";
}
RaisePropertyChanged("Input");
}
}
This fails to update the UI. If I enter "B" and check the debugger, it runs through the setter, but fails to actually update the UI.
Curiously, if I set this.input = "TEST"; in the else block, the UI updates, but, if I attempt to set it to "", string.Empty, or the value of input before the validation, the UI fails to update.
Is this by design? Possibly a bug? Is there something I'm doing wrong?
Edit I mistakenly forgot to include RaisePropertyChanged in my example code. I've updated it. Raising it isn't the problem as I've watched the debugger run all the way through raising it and returning input via the getter.
Way you use strign type property and then convert to decimal, easier to change lik this:
public decimal Input
{
get { return this.input; }
set
{
this.input = value;
RaisePropertyChanged("Input");
}
}
And for validate use IDataErrorInfo (read more: http://blogs.msdn.com/b/wpfsdk/archive/2007/10/02/data-validation-in-3-5.aspx)
What we have done is created a Custom Control, since we use it for a Currency Text Box. I warn you I have no validation that this is a good idea, or falls in line with MVVM model because all manipulation of the control are done in code behind.
In the control on the textbox we have an event on PreviewTextInput that does this
e.Handled = Functions.DoubleConverter(Convert.ToChar(e.Text), ((TextBox)sender).Text.Replace("$", ""));
Then for the function (which isnt perfect, I have a few issues with it still) is:
static public bool DoubleConverter(char c, string str)
{
if (!char.IsDigit(c))
{
if (c == '.' && (str.Contains('.')))
{
return true;
}
else if (c != '.')
{
return true;
}
}
return false;
}
Please use this as a reference, not exactly as is because it is a very crude implementation.
I have a few questions about a use of SelectMany I have encountered in one of the projects I am working on. Below is a small sample that reproduces its use (with a few Console.WriteLines I was using to help see the states at various points):
public partial class MainWindow : INotifyPropertyChanged
{
private bool _cb1, _cb2, _cb3, _isDirty;
private readonly ISubject<Unit> _cb1HasChanged = new Subject<Unit>();
private readonly ISubject<Unit> _cb2HasChanged = new Subject<Unit>();
private readonly ISubject<Unit> _cb3HasChanged = new Subject<Unit>();
private readonly ISubject<string> _initialState = new ReplaySubject<string>(1);
public MainWindow()
{
InitializeComponent();
DataContext = this;
ObserveCheckBoxes();
var initialState = string.Format("{0}{1}{2}", CB1, CB2, CB3);
_initialState.OnNext(initialState);
Console.WriteLine("INITIAL STATE: " + initialState);
}
public bool CB1
{
get
{
return _cb1;
}
set
{
_cb1 = value;
_cb1HasChanged.OnNext(Unit.Default);
}
}
public bool CB2
{
get
{
return _cb2;
}
set
{
_cb2 = value;
_cb2HasChanged.OnNext(Unit.Default);
}
}
public bool CB3
{
get
{
return _cb3;
}
set
{
_cb3 = value;
_cb3HasChanged.OnNext(Unit.Default);
}
}
public bool IsDirty
{
get
{
return _isDirty;
}
set
{
_isDirty = value;
OnPropertyChanged("IsDirty");
}
}
private void ObserveCheckBoxes()
{
var checkBoxChanges = new[]
{
_cb1HasChanged,
_cb2HasChanged,
_cb3HasChanged
}
.Merge();
var isDirty = _initialState.SelectMany(initialState => checkBoxChanges
.Select(_ => GetNewState(initialState))
.Select(updatedState => initialState != updatedState)
.StartWith(false)
.TakeUntil(_initialState.Skip(1)));
isDirty.Subscribe(d => IsDirty = d);
}
private string GetNewState(string initialState = null)
{
string update = string.Format("{0}{1}{2}", CB1, CB2, CB3);
if (initialState != null)
{
Console.WriteLine("CREATING UPDATE: " + update + " INITIAL STATE: " + initialState);
}
else
{
Console.WriteLine("CREATING UPDATE: " + update);
}
return update;
}
public event PropertyChangedEventHandler PropertyChanged;
void OnPropertyChanged(string prop)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(prop));
}
}
private void Button_Click(object sender, System.Windows.RoutedEventArgs e)
{
var newState = GetNewState();
_initialState.OnNext(newState);
Console.WriteLine("SAVED AS: " + newState);
}
}
and the xaml:
<Window x:Class="WpfSB2.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525">
<Grid>
<StackPanel>
<CheckBox IsChecked="{Binding CB1}"></CheckBox>
<CheckBox IsChecked="{Binding CB2}"></CheckBox>
<CheckBox IsChecked="{Binding CB3}"></CheckBox>
<Button IsEnabled="{Binding IsDirty}" Click="Button_Click">APPLY</Button>
</StackPanel>
</Grid>
</Window>
So what this little app does is show three checkboxes (all initially unchecked) and an "Apply" button. When the state of the checkboxes changes, the button should become enabled and when clicked become disabled until the state of the checkboxes changes again. If you change the state of the checkboxes but then change it back to its inital state, the button will enable/disable appropriately. The app works as expected, I am just trying to figure out why/how.
Now the questions:
Will the SelectMany call be triggered whenever the _initialState or a check box change occurs?
The first call of _initialState.OnNext(initialState); (in the constructor) doesn't really do anything when it comes to the SelectMany code. I see it makes its way to the SelectMany code but nothing is actually done (I mean, if I put a breakpoint on the checkBoxChanges.Select section it breaks but nothing is actually selected). Is this because no changes have occured to any of the checkboxes yet?
As expected, checking any checkBox triggers the isDirty check. What exactly is happening in this SelectMany statement the first time I change a single checkbox?
After checking a box, the Apply button becomse enabled and I hit Apply. This causes _initialState.OnNext(newState); to be called. Similar to my first question, nothing seems to happen in the SelectMany statement. I thought with the initial state getting a new value something would get recalculated but it seems to go straight to the OnNext handler of isDirty.Subscribe(d => IsDirty = d);
Now that I hit Apply, _initialState.OnNext has been called twice in total. If I check a new checkbox, how does the SelectMany handle that? Does it go through all of the past states of _initialState? Are those values stored until the observable is disposed?
What are the StartsWith/TakeUntil/Skip lines doing? I noticed that if I remove the TakeUntil line, the app stops working correctly as the SelectMany clause starts going through all the past values of _initialState and gets confused as to which is the actual current state to compare to.
Please let me know if you need additional info.
I think the key part of your problem is your understanding of SelectMany. I think it is easier to understand if you refer to SelectMany as "From one, select many".
For each value from a source sequence, SelectMany will provide zero, one, or many values from another sequence.
In your case you have the source sequence that is _initialState. Each time a value is produced from that sequence it will subscribe to the "inner sequence" provided.
To directly answer your questions:
1) When _initialState pushes a value, then the value will be passed to the SelectMany operator and will subscribe to the provided "inner sequence".
2) The fist call is putting the InitialState in the ReplaySubject's buffer. This means when you first subscribe to the _initialState sequence it will push a value immediately. Putting your break point in the GetNewState will show you this working.
3) When you check a check box, it will call the setter, which will OnNext the _cbXHasChanged subject (yuck), which will flow into the Merged sequence (checkBoxChanges) and then flow into the SelectMany delegate query.
4) Nothing will happen until the check boxes push new values (they are not replaysubjects)
5-6) Yes you have called it twice so it will run the selectMany delegate twice, but the TakeUntil will terminate the first "inner sequence" when the second "inner sequence" is kicked off.
This is all covered in detail on (my site) IntroToRx.com in the SelectMany chapter.
I have a little problem with my component. This looks like my property:
private ViewType _viewType = ViewType.Week;
public ViewType DisplayType
{
get { return _viewType; }
set
{
_viewType = value;
if (panelKalendar != null)
panelKalendar.Invalidate();
}
}
and this I have in Kalendar_Load():
...
if (this._viewType == ViewType.Month)
panelKalendar.Top = yPoloha;
else if (this._viewType == ViewType.Week)
panelKalendar.Top = yPoloha + VYSKA_BUNKY;
...
(class ViewType)
public enum ViewType
{
Week,
Month,
}
when I add my component to app It looks fine. I have default week, my component is in design draw with week looks. But when I change it to Month, in Design it is shown bad, it gets the second if in kalendar_load (panelKalendar.Top = yPoloha + VYSKA_BUNKY). When I built it, it´s ok. And that´s problem. Why Designer use default property and not that which is set? Thanks
Load is not called when a component is in the designer. Code in properties and in the constructor are. If you want changing the viewType property to cause a change in the designer you will need to use your code when the property is set. Instead of during load. That should also work for you at run time.
I would create a method like this:
private void UpdateViewType()
{
if (this._viewType == ViewType.Month)
panelKalendar.Top = yPoloha;
else if (this._viewType == ViewType.Week)
panelKalendar.Top = yPoloha + VYSKA_BUNKY;
}
and then in the property when the value is set call it
...
set
{
_viewType = value;
UpdateViewType()
if (panelKalendar != null)
panelKalendar.Invalidate();
}