I am working on a WPF application similar to visio. I would like to be able to logically group some of the items in my diagram, each of which is a UIElement, and control certain behaviors (i.e. visibility) at the group level.
My first attempt at this was to create a control, called a Group, which had width & height = 0. I wanted to assign to my diagram elements a specific "Group" through their group property, and then bind certain UIElement properties to the group value, as below:
<DiagramNode
Width="300" Height="300"
Visibility="{Binding RelativeSource={RelativeSource Self},Path=Group.Visibility}"
> ... </DiagramNode >
Although this does not throw a binding error, it also doesn't work. Changing the Visibility of the group has no affect on the visibility of the nodes assigned to that group. No errors appear at anytime as far as i can tell, it just doesn't work.
Any ideas? Is my approach possible? If no, any one have alternatives they'd like to suggest :). I'm not a huge UI guy, feel much more comfortable in a service layer, so I'm open to other suggestions.
If there really is no binding error in the trace of the application when run through the debugger, then the problem is probably in change notifications. You must make sure that the Visibility property of your Group object provides change notifications when changed.
This is usually done by implementing INotifyPropertyChanged on the class, and in the set accessor raising a PropertyChanged event (if the value actually changed).
Is the issue perhaps in my property declaration of the Group object of my DiagramNode class?
Public Class DiagramNode
...
Private _group As Group
Public Property Group() As Group
Get
Return Me._group
End Get
Set(ByVal value As Group)
Me._group = value
End Set
End Property
...
End Class
Related
I am designing a WPF application following MVVM. My ViewModel is exposing one Double property called DoubleValue, which is binding to a TextBox in the View. I have set "ValidatesOnDataErrors=True" for the binding. So if the user types a string which can't be converted to a Double, it display the red background.
In my ViewModel I also have a Command object, let's call SaveCommand, whose CanExecute delegate is depending on whether there is any error in the VM (my ViewModelBase class implements IDataErrorInfo, I have an overridable ValidatePropertyByName function and the validation errors are stored in a dictionary.) But now my problem is, if I give an invalid string in the TextBox, since the conversion fails, it never calls the setter of the binding property value. In another word, the ValidatePropertyByName is not called and the error dictionary remains the previous state, which normally is clean. So if now the user click the Save button (which is enabled since the error dictionary is clean), the SaveCommand executes with the previous valid double value to save. This is obviously not good.
So how can I make my ViewModel aware of such conversion errors?
UPDATE:
Some code example:
The binding property is like this:
Public Property DoubleValue As Double
Get
Return _doubleValue
End Get
Set(value As Double)
If value <> _doubleValue Then
_doubleValue = value
RaisePropertyChanged("DoubleValue")
End If
End Set
End Property
Private _doubleValue As Double
My binding is like this:
<TextBox Grid.Row="3" Text="{Binding DoubleValue, ValidatesOnDataErrors=True}" />
And now my problem is: if I give a string "XXX" in the text box, since it can't be converted to a double value, the setter of DoubleValue is never get called. And so the property value remains the previous(valid) value. Now if my SaveCommand gets executed, it will do the save operation with this previous valid value, which will make the user confused.
the most easy way is to just use string properties in your viewmodel. then you get all input from the user and can validate it in your viewmodel. the drawback is that you have to convert the values to the right type when you go to the model.
if you dont want this you have to create your own controls or better behaviors so that the use can just input values that your viewmodel expect. eg. NumericInputBehavior.
You cannot simply put these two things together. One is the regular validation inside the ViewModel. The other are control-specific problems, like unconvertible values.
So there are two possible ways to solve this:
1) Don't use a converter. Just bind the string. Inside the ViewModel you can then use the validation to check for a valid value. (More MVVM)
2) Store your ValidationErrors on the controlside and merge them with the viewmodel errors. This is not easy but a good way to create one source for binding against ALL problems within your UI. We are doing this for complex textboxes at work. This means manual code in the controls but for complex customcontrols this is OK, I believe.
edit: just to elaborate a little on the 2nd point. We are having a DependencyProperty of Type ObservableCollection inside the Control. Then you can bind this Collection to a ViewModel Property and as soon as your control moves an Error inside the collection it is available inside the viewModel. You can then use this collection inside your validation implementation. This works pretty well for larger controls.
Edit2: For the MarkInvalid Stuff I mentioned in the comment. It would look like this:
DataErrorValidationRule validationRule = new DataErrorValidationRule();
ValidationError validationError = new ValidationError(validationRule, myTextBox.GetBindingExpression(TextBox.TextProperty)) { ErrorContent = "My custom message" };
Validation.MarkInvalid(myTextBox.GetBindingExpression(TextBox.TextProperty), validationError);
You would call in from inside a TextChanged when you can't convert the new given value or
Validation.ClearInvalid(myTextBox.GetBindingExpression(TextBox.TextProperty))
Maybe that will help?
I've started to make myself a list of "WPF gotchas": things that bug me and that I had to write down to remember because I fall for them every time....
Now, I'm pretty sure you all stumbled upon similar situations at one point, and I would like you to share your experience on the subject:
What is the gotcha that gets you all the time? the one you find the most annoying?
(I have a few issues that seem to be without explanation, maybe your submissions will explain them)
Here are a few of my "personnal" gotchas (randomly presented):
For a MouseEvent to be fired even when the click is on the "transparent" background of a control (e.g. a label) and not just on the content (the Text in this case), the control's Background has to be set to "Brushes.Transparent" and not just "null" (default value for a label)
A WPF DataGridCell's DataContext is the RowView to whom the cell belong, not the CellView
When inside a ScrollViewer, a Scrollbar is managed by the scrollviewer itself (i.e. setting properties such as ScrollBar.Value is without effect)
Key.F10 is not fired when you press "F10", instead you get Key.System and you have to go look for e.SystemKey to get the Key.F10
... and now you're on.
Always watch the output window for
binding errors. Ignoring the output
window is a recipe for tears.
Use PresentationTraceOptions.TraceLevel="High" in a binding to get verbose binding information when debugging binding failures.
Make static, unchanging resources such as brushes PresentationOptions:Freeze="True" to save resources at runtime.
Use the WPF DataGrid as a datagrid. Modifying it to behave like Excel is a massive pain in the butt.
BindingList<T> does not play well with CollectionViewSource. Expose ObservableCollection<T> from your viewmodels instead.
The internet supplies half a dozen different ideas for displaying CueBanner text in a WPF textbox. They are all broken.
1) One that used to get me every half an hour when I was making my transition from WinForms: use TextBlock instead of Label when putting random text on the UI (or don't use any tag at all, if the text is static)!
2) DataTriggers/Triggers can't be put into Control.Triggers, but have to go into Control.Styles/Style/Style.Triggers
3) Property's type must implement IList, not IList<T>, if the property is to be recognized by XAML as a collection property.
4) Bindings capture exceptions.
5) Use singleton converters/static converter class, so you don't have to create a new converter every time you use it.
6) A type for default value of DependencyProperty has to be clearly specified: 0u as uint, (float) 0 as float, 0.0 as double...
7) It matters if the control's property definitions are before or after its content.
8) NEVER use PropertyMetadata to set a default value of reference type DependencyProperty. The same object reference will be assigned to all instances of the owning class.
When first starting out, the main gotchas that would get me would be
Lists not updating due to forgetting
to use ObservableCollection
Properties not being updated either
forgetting to add OnPropertyChanged
or incorrectly typing the property
name
Recently I have stumbled across these issues
Application failing to start due to
corrupt font cache
StringFormat localization issues
If enabled, Button.IsCancel assigns false to Window.DialogResult but Button.IsDefault no.
They are so similar and for me it seemed intuitive at first that both should close dialog. I usually break MVVM and fix this in code-behind
Button.IsCancel + Command = Dialog won't close (Window.DialogResult left unassigned) but Command executes
As I understand it: If IsCancel had higher priority than Command then on Esc it would assign 'false' to DialogResult and Command won't be called. Or, if Command would have higher priority then it would be called first and DialogResult would be assigned. I don't understand how it is skipped?
Binding swallows exceptions!
It not only steals time while debugging it is also wrong from the OOP point of view because if exception is thrown it means that something exceptional had happened somewhere in our system (anything from wrong data supply to unauthorized access to memory failure) so it can be handled only if you know what to do. You can't just catch(Exception){} catch 'em all and then ignore. If there is unknown exception in program it should notify user, log and close not pretend like everything is ok...
HeaderContent can have only one child control and has no padding
Everything should have padding even logical controls (containers), right? I think it is inconsistent. What do you think?
If you set focus to ListBox via FocusManager.FocusedElement you still won't be able to switch it's content with keyboard because focus is set to ListBoxes frame not it's content. I think I don't know other UI API that would expose something like controls frame to UI programmer it should be encapsulated from us because abstractly ListBox represents a list, it is just a list of things not a list of things in a box. ok it has box in its name but still... We almost have two different controls here.
MVVM not breaking fix
ListBox.IsSynchronizedWithCurrentItem by default is false so if you assign different value or null to ItesSource then SelectedItem still holds old value until user selects something from a new list. It could mess up CanExecute for example. Need to set it every time by hand.
No binding exposed in PasswordBox results in time waste and dirty hacks... But still it has a string property PasswordBox.Password exposed so don't even try to argue about security because Snoop...
It is not a gotcha but table layout is so IE6 IMO. Container design helps separate content from its layout.
Because every time I need to change something in places I need to mess up with Grid.Row and Grid.Column. Yes, we have DockPanel, StackPanel and others but you can't do some column alignment inside of them. (And DockPanel is like completely separate gotcha) If UniformGrid would be more customizable it would be ideal I think. You always need to choose between Grid and Panels and usually if you gain something you loose something else.
I got a pretty nifty one last week:
When Templating a RichTextBox, the event handling inside the template follows a strange route that has nothing to do neither with tunnelling nor bubbling
e.g.: In the case of an event that is supposed to tunnel: the event first tunnels through the ContentPresenter, then it tunnels back from the top of the template.
see my question on the subject
ToolTips and ContextMenus not sharing the DataContext of its owner? I think that gets everyone at first
There is no clean way to handle validation in WPF, I am not a fan of magic string which IDataErrorInfo offers by default:
public string this[string columnName]
{
if (columnName == "FirstName")
{
if (string.IsNullOrEmpty(FirstName))
result = "Please enter a First Name";
}
}
However, I have tried many frameworks like SimpleMVVM, FluentValidation and MVVMValidation and BY FAR MVVM Validation is the best getting to do stuff like:
Validator.AddRule(() => RangeStart,
() => RangeEnd,
() => RuleResult.Assert(RangeEnd > RangeStart, "RangeEnd must be grater than RangeStart");
My personal favorite is this one:
public double MyVariable
{
get { return (double)GetValue(MyVariableProperty); }
set { SetValue(MyVariableProperty, value); }
}
public static readonly DependencyProperty MyVariableProperty = DependencyProperty.Register(
"MyVariable", typeof(double), typeof(MyControl), new UIPropertyMetadata(0));
Try it, once this property is declared it will crash. Why? Because 0 can't be assigned to a double using reflection apparently.
Not really a gotcha but an advice: Use Snoop or something similar, if you don't use it you must be crazy ... Crazy i tell ya!
Binding.StringFormat only works if the type of the target property is string.
TreeView's SelectedItem property is not settable. Instead you have to bind TreeViewItem's IsSelected property to your item's viewmodel and set your selection there.
ListBox's SelectedItem, on the other hand is settable, but item selection is not equal to item focus. If you want to implement proper keyboard navigation along with selecting items from within viewmodel, you have to implement manual focus fix, like:
public void FixListboxFocus()
{
if (lbFiles.SelectedItem != null)
{
lbFiles.ScrollIntoView(lbFiles.SelectedItem);
lbFiles.UpdateLayout();
var item = lbFiles.ItemContainerGenerator.ContainerFromItem(viewModel.SelectedFile);
if (item != null && item is ListBoxItem listBoxItem && !listBoxItem.IsFocused)
listBoxItem.Focus();
}
}
...and call it every time you change selected item from viewmodel:
SelectedFile = files.FirstOrDefault();
viewAccess.FixListboxFocus();
I am trying to properly accomplish the following. I have a UserControl (ProgramView). It has a viewmodel (ProgramViewViewModel). ProgramView is consumed as a child control within a Window (ProgramWindow). ProgramWindow has a public property ProgramId, so the consumer of the window can specify the desired Program (data entity) to show. ProgramView has a property ProgramId, as it's primary job is to display this data. ProgramWindow is little more than a wrapper window for this user control.
ProgramViewViewModel also has a property ProgramId. Changes to this property drive out the operation of the view model, which are surfaced out of the view model using other properties, which ProgramView can bind to.
I am trying to hide the operation of the view model from the consumer of the ProgramView and ProgramWindow.
This ProgramId should be bound through all of these layers. Changes to ProgramWindow.ProgramId should flow to ProgramView.ProgramId and then to ProgramViewViewModel.ProgramId. I cannot figure out how to properly implement this.
My current approach is to surface ProgramId in all three classes as a DP. Within the Window, I would imagine ProgramView being instantiated thusly:
<local:ProgramView ProgramId="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:ProgramWindow}}, Path=ProgramId}" />
This appears to actually work. Within ProgramView, I do obtain changed events for the property, and they do appear to have the correct value. FindAncestor seems to operate properly.
How then should I synchronize the ProgramViewViewModel.ProgramId property? I see two ways. One way would be to set a Binding on the ProgramViewViewModel instance itself, to also use FindAncestor, and find the ProgramId on the ProgramViewViewModel This has two downsides. It requires ProgramViewViewModel to surface ProgramId as a dependency property. I'd rather like to avoid this, but it might be acceptable. Either way, I cannot accomplish it in XAML.
<local:View.DataContext>
<local:ProgramViewViewModel
ProgramId="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:ProgramView}}, Path=ProgramId}" />
</local:View.DataContext>
This does not work. It appears that I cannot introduce a binding expression within the instantiation of the instance. FindAncestor reports that it cannot find ProgramView. My theory here is that the instance is outside of the logical tree, and thus cannot traverse to it's parent.
The second option, which makes more sense, is to bind the ProgramView.ProgramId property to "ProgramId" (in the DataContext). I cannot accomplish this because I cannot figure out how to specify a binding expression on a property defined in the code-behind. is required in the XAML, but the type ProgramId exists on is actually . I cannot figure out how to specify this property.
If I manually (in code-behind of ProgramView) create a Binding instance and call SetBinding(ProgramIdProperty, binding), the value no longer flows into the View itself. I believe this is because I just replaced the binding on ProgramView.ProgramId, which was previously set by ProgramWindow. One binding per-property?
My remaining ideas are to provide TWO ProgramId properties in ProgramView. One bound to the DataContext, the other publicly available to be bound by the consumer (ProgramWindow), and then write OnValueChanged handlers that synchronize the two. This feels like a hack. The other is to manually watch for changes on ProgramView.ProgramId and ProgramView.DataContext within the code-behind of ProgramView, and propagate the value myself. Neither of these ideas feel ideal.
I'm looking for other suggestions.
Your description seems detailed but I'm having trouble understanding why you need to implement this design. I can't help but think DRY.
If you need to expose a dependency property in two such-related view models, I would suggest that you make the child view model (for the user control view) a property of the first (for the program window view). Something like:
public class MainViewModel : ViewModelBase
{
public ProgramViewModel ChildViewModel { get; private set; }
}
public class ProgramViewModel : ViewModelBase
{
private int _ProgramId;
public int ProgramId
{
get { return _ProgramId; }
set
{
if (value != _ProgramId)
{
// set and raise propery changed notification
}
}
}
}
The MainView can get the property using ChildViewModel.ProgramId (data context set to MainViewModel). The ProgramView accesses it by ProgramId (data context set to MainViewModel.ChildViewModel).
I'm having one hell of a time trying to get my databinding to work correctly. I have reason to believe that what I'm trying to accomplish can't be done, but we'll see what answers I get.
I've got a UserControl. This UserControl contains nothing more than a button. Now within the code behind, I've got a property name IsBookmarked. When IsBookmarked is set, code is run that animates the look of the button. The idea is that you click the button and it visually changes. We'll call this UserControl a Bookmark control.
Now I have another control, which we'll call the FormControl. My FormControl contains a child Bookmark control. I've tried to do databinding on my Bookmark control, but it's not working. Here's some code to help you out.
This is the XAML and Loaded event handler of my control. As you can see it contains a child element that is a custom control (bookmark). So once this control loads, it's DataContext is set to an new instance of an Employee object. Silverlight also sets the DataContext property of my child bookmark control to the same instance. I've verified this by debugging. If my parent has a valid DataContext set then why can't my child control (bookmark) property databind to it?
<UserControl ......>
<q:Bookmark x:Name="BookMarkControl1" IsBookmarked="{Binding IsSiteBookmarked}" />
</UserControl>
public void Control_Loaded(object sender, EventArgs e)
{
DataContext = new Employee { IsSiteBookmarked = True };
}
This is my custom control below. Obviously it contains more than this, but for readability I've trimmed it down to the property I'm trying to databind to.
//this is the bookmark control. I've included this control within another control, and I'm trying to databind to properties within my parents DataContext
public partial class Bookmark : UserControl
{
bool _IsBookmarked= false;
public bool IsBookmarked
{
get {return _IsBookmarked;}
set {
_IsBookmarked= value;
SwitchMode(value);
}
}
}
UPDATE
Got some javascript errors that I should mention. Firebug reports a AG_E_PARSER_BAD_PROPERTY_VALUE exception. It doesn't seem like my databinding is even working yet.
Make your IsBookmarked property on the Bookmark control a dependency property.
I presume Control_Loaded is a part of your FormControl, in which case I'm not sure you are using DataContext properly. Best double check that.
UPDATE: Yes, you are using the DataContext properly. AG_E_PARSER_BAD_PROPERTY_VALUE indicates you need to make the IsBookmarked property a dependency property, like so:
Public Property IsBookmarked() As Boolean
Get
Return Me.GetValue(IsBookmarkedProperty)
End Get
Set(ByVal value As Boolean)
Me.SetValue(IsBookmarkedProperty, value)
End Set
End Property
Public Shared ReadOnly IsBookmarkedProperty As DependencyProperty = DependencyProperty.Register("IsBookmarked", GetType(Boolean), GetType(Bookmark), New PropertyMetadata(New PropertyChangedCallback(AddressOf OnIsBookmarkedPropertyChanged)))
Private Shared Sub OnIsBookmarkedPropertyChanged(ByVal d As DependencyObject, ByVal e As DependencyPropertyChangedEventArgs)
Dim cntrl As Bookmark = TryCast(d, Bookmark)
cntrl.SetIsBookmarked(e.NewValue)
End Sub
If you only need to store the value for later use, then you don't need to do anything in the OnIsBookmarkedPropertyChanged procedure, But I put some code there as an example anyway.
Good Luck!
I don't recall the exact order in which databinding is evaluated (and I'm too lazy to go look it up), but as I recall, it initially happens BEFORE the form's Loaded event fires, and without making the IsBookmarked property a dependency property, or at least using INotifyPropertyChanged, it may have trouble establishing the datacontext appropriately. I'd recommend either implementing INotifyPropertyChanged or making IsBookmarked a dependency property. DataBinding is tough enough to get right in the best of circumstances (see my long, bad-tempered rant about it here), and you'll just be making it more difficult on yourself if you aren't setting up your properties in the way that it expects.
The control exposes a IsSiteBookmarked property(which I believe should be a DependencyProperty) but the control is binding to a IsBookmarked which is not shown. Is this intentional? Have you checked your Visual Studio output window for binding errors?
Addition 1:
Since you have fixed the typo in your question and added that there is an error being reported.
Start by clearing up the AG_E_PARSER_BAD_PROPERTY_VALUE problem. Is there a line number and start position in the error message? Start looking there. One strategy is to start taking out XAML until there is no longer an error. This will narrow down the offending code.
Running in debug, mode check for binding errors in the output window.
You might want to also post the Employee class code, especially the IsSiteBookmarked property.
Typically when doing databinding to an object you will want to leverage the INotifyPropertyChanged interface and implement that so that the control can properly invalidate it's property value. Unless you use INotifyPropertyChanged with Mode=TwoWay then any code that changes your DataContext's IsSiteBookmarked will have no effect.
I have a an object which is set to the DataContext in a Window. I have textboxes in the window which are bound to the properties on the object. There seems to be a delay however before the properties on the object are updated.
<TextBox x:Name="txtPropertyOne" Text="{Binding Path=PropertyOne,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}" />
If I change the values in a few textboxes then quickly try to access the properties to which they map, sometimes there are changes which aren't reflected in the properties of the object. I thought that was what the PropertyChanged UpdateSourceTrigger was supposed to take care of.
If I change the values in a few
textboxes then quickly try to access
the properties to which they map
I can interpret this statement in two ways:
You're trying to access the values on a background thread. In that case, you may be accessing the properties before the UI thread has had a chance to do its thing.
You're using a separate message on the UI thread to check the values. Bindings are updated at a priority lower than Send and Normal. So if your message is priority Send or Normal it will be processed before any pending binding updates.
If this doesn't answer your question, please clarify what you mean by "quickly trying to access the properties".
The basic rule of WPF Databinding is simple:
The target property must be a
dependency property, and you're
already correct, it's bound to Text
property of TextBox.
The source property can be a CLR
object (other than any derived WPF's
DependencyObject), but the object
must employ or implement its own
INotifyPropertyChanged.
Have you you already implemented INotifyPropertyChanged on your object?