Validation.Error deletes all when ContentPresenter changes content - wpf

This is a WPF, .Net 4.8 app. I have a control that's similar to tabcontrol in that there's a ContentPresenter:
<ContentPresenter Content="{Binding SelectedTab.Content, RelativeSource={RelativeSource TemplatedParent}, Mode=TwoWay}" />
If you click a certain item in a list, Content is switched to another item (the SelectedTab) held in a dependency property that looks like this:
public static readonly DependencyProperty TabsProperty = DependencyProperty.Register(nameof(Tabs), typeof(ObservableCollection<MyTab>), typeof(MyTabLikeControl), new PropertyMetadata(null));
MyTab is just a class like so:
[ContentProperty(nameof(Content))]
public class MyTab : DependencyObject
{
#region Title
public string Title
{
get => (string)GetValue(TitleProperty);
set => SetValue(TitleProperty, value);
}
public static readonly DependencyProperty TitleProperty =
DependencyProperty.Register(nameof(Title), typeof(string), typeof(MyTab), new PropertyMetadata(null));
#endregion Title
#region Content
public object Content
{
get => GetValue(ContentProperty);
set => SetValue(ContentProperty, value);
}
public static readonly DependencyProperty ContentProperty =
DependencyProperty.Register(nameof(Content), typeof(FrameworkElement), typeof(MyTab), new PropertyMetadata(null));
#endregion Content
}
You use the control like:
<m:MyTabControl>
<m:MyTabControl.Tabs>
<m:MyTab Title="Tab A">
...XAML content...
</m:MyTab>
<m:MyTab Title="Tab B">
...XAML content...
</m:MyTab>
<m:MyTab Title="Tab C">
...XAML content...
</m:MyTab>
<m:MyTabControl.Tabs>
</m:MyTabControl>
The malfunction is to do with validation. When the SelectedTab changes to one of the other items in the Tabs collection, the Validation.Error fires (x times), deleting all validation errors (using IDataErrorInfo) on the tab we're leaving. That's BAD because I am using the Validation.Error event to count the number of UI elements with errors and display it so the user knows which tabs contain validation errors.
Once I re-select the original tab (and the ContentPresenter displays it), Validation.Error fires again (x times) so I can get the error count again. But of course the other tab then loses all its errors (the event fires)!
Moreover, none of other tabs never get their error counts until they're actually selected (and the ContentPresenter displays them).
So it's like the validation system doesn't recognize the existence of the binding errors because that UI isn't displayed at that moment, even if the UI still exists. The visual state of the non-selected tabs are never lost so they not being deleted and recreated or anything like that (they're stored in the Tabs Dependency Property shown above).
Is there perhaps a way to keep the non-selected tabs in the visual tree when they're not being displayed by my ContentPresenter, and to create them in the visual tree initially? I want them all available immediately so that all tabs can show their error counts and all be updated live regardless which tab is selected.
I tried a third party validation lib called Gu.Wpf.ValidationScope but it suffered the same problem. So now I'm using Validation.Errors manually with the same effect.

I gave up trying to get this to work with my custom control. Instead, I found it easiest to to restyle the TabControl and TabItem, which don't have this issue because they act like an ItemsControl.
TabControl has lots of useful keyboard behaviors anyway.

Related

Order In Chaos Of Control Initialization Steps

I would like to know when is actually what happening inside initalization process of controls when I start a WPF application?
When are DP initalized? When Binding? When does DataContext get set? Is DataContext avaialbe in constructor of a control? Is there any kind of order?
I realized I ran into a trap that once I set a value on getter/setter of a DP inside constructor of a control the DP value gets updated but immediately also the values gets rolled back to default value which was null.
So my guess is that contructors get initalized first and then dependency properties.
Can somebody help me out with this?
Edit: Just for Rachel. The dp receives the value 234 and immedialty rolls back to null. I think its because constructor gets called first and then subsequently the initalizing of dps happens which sets dp back to null because null is default value. Am i thinking wrong about this? What is the order of initalization steps of a control or dependency object.
class MySuperDuperCoolClass : ContentControl
{
public MySuperDuperCoolClass()
{
InitalizeComponents();
this.MySuperDuperProperty = "234";
}
public string MySuperDuperProperty
{
get { return (string)GetValue(MySuperDuperPropertyProperty);}
set { SetValue(MySuperDuperPropertyProperty, value);}
}
public static DependencyProperty MySuperDuperPropertyProperty =
DependencyProperty.Register("MySuperDuperProperty", typeof(string), typeof(MySuperDuperCoolClass),
new PropertyMetadata(null));
}
I find the DispatcherPriority Enum useful for recalling the exact event order:
Send
Normal - Constructors run here
DataBind
Render
Loaded
Background
ContextIdle
ApplicationIdle
SystemIdle
Inactive
Invalid
Input
As you can see, Constructors get run first, followed by data bindings.
DependencyProperties get initialized when the object gets created, just like any other property, so that would occur prior to the constructor being run so the property exists in the constructor.
Setting the DataContext property or other DependencyProperties works just like any other property you are setting. If you set them with a binding, they'll get evaluated after the constructor. If you set them in the XAML, they'll get set in the Constructor. If you set them in the Loaded event, they'll get set after everything has been constructed, bound, and rendered.
You also might find this SO answer useful:
Sequence of events when a Window is created and shown
As requested, here is the sequence of major events in WPF when a
window is created and shown:
Constructors and getters/setters are called as objects are created, including PropertyChangedCallback, ValidationCallback, etc on the
objects being updated and any objects that inherit from them
As each element gets added to a visual or logical tree its Intialized event is fired, which causes Styles and Triggers to be
found applied in addition to any element-specific initialization you
may define [note: Initialized event not fired for leaves in a logical
tree if there is no PresentationSource (eg Window) at its root]
The window and all non-collapsed Visuals on it are Measured, which causes an ApplyTemplate at each Control, which causes additional
object tree construction including more constructors and
getters/setters
The window and all non-collapsed Visuals on it are Arranged
The window and its descendants (both logical and visual) receive a Loaded event
Any data bindings that failed when they were first set are retried
The window and its descendants are given an opportunity to render their content visually
Steps 1-2 are done when the Window is created, whether or not it is
shown. The other steps generally don't happen until a Window is
shown, but they can happen earlier if triggered manually.
Edit based on code added to question
Your DependencyProperty.Register method looks funny to me. The signature of the method doesn't match any of the overloads for that method, and you're using what appears to be a custom UIProperty class to set the default value instead of the normal PropertyMetadata.
I can confirm that if your code runs as expected with a normal DependencyProperty.Register signature, so the likely cause of your problem is either somewhere within your custom code, or its with how you are using/setting the property.
The code I used for a quick sample test is this:
public partial class UserControl1 : ContentControl
{
public UserControl1()
{
InitializeComponent();
this.TestDependencyProperty = "234";
}
public string TestDependencyProperty
{
get { return (string)GetValue(TestDependencyPropertyProperty); }
set { SetValue(TestDependencyPropertyProperty, value); }
}
public static DependencyProperty TestDependencyPropertyProperty =
DependencyProperty.Register("TestDependencyProperty", typeof(string), typeof(UserControl1),
new PropertyMetadata(null));
}
and the XAML is
<ContentControl x:Class="WpfApplication1.UserControl1"
x:Name="TestPanel" ...>
<Label Content="{Binding ElementName=TestPanel, Path=TestDependencyProperty}"/>
</ContentControl>
In WPF you are setting default values for DP with PropertyMetaData not via constructor.
public partial class UserControl1 : ContentControl
{
public UserControl1()
{
InitializeComponent();
}
public string TestDependencyProperty
{
get { return (string)GetValue(TestDependencyPropertyProperty); }
set { SetValue(TestDependencyPropertyProperty, value); }
}
public static DependencyProperty TestDependencyPropertyProperty =
DependencyProperty.Register("TestDependencyProperty", typeof(string), typeof(UserControl1),
new PropertyMetadata("234"));
}

Set controls on wpf form to readonly or editable

I am writing a data entry application and am having problems dealing with add vs edit vs view 'modes' of the app.
The scenario: From a window, the user selects items in two combo boxes and then clicks a button. If the combo box selections don't match an existing record, the form should open with a new record ready to have values inserted. If the selections DO match an existing record, this record should be opened with all the textboxes in the form in 'Readonly' mode. Should the user wish to modify the existing data, they click the form's Edit button which sets the textboxes as 'Editable'.
I'm new to wpf, but in my vba apps, I'd normally have a DataMode property and a ToggleControls method which would loop through the TabControl's TabItems and then loop through the TabItems' controls, setting the IsReadOnly property of any TextBox controls found based on the 'DataMode' property. However, I have doubts about whether this is the path to follow in wpf.
So, my question is, do i use the approach above? If so, how do I access the controls in a TabItem. This doesn't work: foreach (Control ctrl in MyTabCtrl) { //if Textbox do stuff }.
If this isn't the way to do it, can someone show me the way? I'm guessing it's either a template / style issue or something to do with setting the data as readonly somehow, and then binding the TextBox's IsReadOnly property to the state of the data. Edit: Oh, or that ViewStateManager thing. All of which i know little about.
An easy approach would be
In Code behind declare a DependencyProperty named IsReadOnly
public static readonly DependencyProperty IsReadOnlyProperty = DependencyProperty.Register(
"IsReadOnly",
typeof (bool),
typeof (MainWindow)
);
public bool IsReadOnly
{
get { return (bool) GetValue(IsReadOnlyProperty); }
set { SetValue(IsReadOnlyProperty, value); }
}
Implement the Buttons Click event, so that IsReadOnly gets false on Click
private void Button_Click(object sender, RoutedEventArgs e)
{
IsReadOnly = false;
}
Bind the TextBoxes to the IsReadonly property using DataBinding
<TextBox IsReadOnly="{Binding Path=IsReadOnly, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=Window}, Mode=OneWay}" />
This binding assumes that your code behind is not your DataContext and you therefore have to bind relatively to the Window/ UserControl which the CodeBehind is applied to.
This way the TextBoxes listen to changes on this property. When it switches, they change their ReadOnly state automatically.
You can use DataContext :
Bind the datacontext of your tabControl to your record (with the button event click write
this.Canvas.DataContext = _yourRecord for sample)
public void bntEvent_Click(object sender, EventArgs e)
{
// Do matching and put the result in result field
MyTabCtrl.DataContext = false;
}
Bind IsReadOnly/IsEnable property of your textboxs on your datacontext and use a converter to return true/false value
<TextBox IsReadOnly="{Binding .}" />
See http://msdn.microsoft.com/en-us/library/system.windows.data.ivalueconverter.aspx for more informations about Converters.
I hope I help you and you understand my bad English.
Good bye

Silverlight: How to add a Dependency Property to multiple controls?

Is it possible to create a Dependency Property for multiple controls without resorting to subclass every one of it?
I thought about using Attached Properties but they nest only one level deep as far as I understand it.
I want something like this to be possible:
<!-- MyDataGrid implements the new Attached Properties SourceData and TargetData -->
<MyDataGrid>
<StackPanel>
<TextBox MyDataGrid.SourceData="{Binding Somewhere}" MyDataGrid.TargetData="{Binding Somewhere}" />
</StackPanel>
<CheckBox MyDataGrid.SourceData="{Binding Somewhere}" MyDataGrid.TargetData="{Binding Somewhere}" />
</MyDataGrid>
This won't work since the Attached Properties wouldn't be found in the TextBox since it's no direct descendent of MyDataGrid.
Background is that I try to automatically convert an old Xaml-like Gui-syntax into real Xaml and with the old system it was possible to set different sources and targets for changed data. Now I'm searching for a Xaml-solution that doesn't involve subclassing every control there is.
Thanks in advance.
are you sure you are using Attached property correctly?
public static readonly DependencyProperty SourceDataProperty = DependencyProperty.RegisterAttached(
"SourceData", typeof (string), typeof (MyDataGrid), new PropertyMetadata("test"));
public static void SetSourceData(DependencyObject obj, string sourceData)
{
obj.SetValue(SourceDataProperty, sourceData);
}
public static string GetSourceData(DependencyObject obj)
{
return (string) obj.GetValue(SourceDataProperty);
}
This worked for me.Though SetSourceData was not get called, but data was there.
To retrive data.
MyDataGrid.GetSourceData(tbox);
Where tbox is the instance of your TextBox.

WPF textbox binding disappears after navigating forward and back again

I have a WPF-application with a mainwindow and different pages. On one of the pages (OverzichtPage) I have a textBox bound to a DataController (Data). (which is a dependencyProperty on the codebehind of the page) (Might be worth mentioning: the DataController is a Singleton, so that patient is supposed to stay the same and can't disapear..)
public static DependencyProperty data = DependencyProperty.Register("Data", typeof(DataController), typeof(OverzichtPage));
public DataController Data
{
get { return (DataController)GetValue(data); }
set { SetValue(data, value); }
}
<TextBox Name="naamPatientTxtBox" Text="{Binding Path=Data.Patient.naam, Mode=TwoWay}" DataContext="{Binding ElementName=OP}" />
At first sight, this binding seems to work. When I navigate to another page by clicking a button
<Button Content="Meer info/ Wijzigen" Click="MeerInfoWijzigenBtn_Click" />
private void MeerInfoWijzigenBtn_Click(object sender, RoutedEventArgs e)
{
Uri pageFunctionUri = new Uri("View/ZorgTrajectPage1.xaml", UriKind.Relative);
NavigationService.Navigate(pageFunctionUri);
}
and navigate back, the binding suddenly stops working. I found out after the navigating back, the naamPatientTxtBox.GetBindingExpression(TextBox.TextProperty).ParentBinding; is empty. Does anyone have a clue why this binding suddenly disapears after the navigating? I really don't understand how this is possible.
Have you tried setting the KeepAlive property of the page to true?
You might be running into history/caching problems. State isn't autmatically kept.
I would put a breakpointat the Loaded event of the parent container (current Window or Page), check the DataContext property (does it contain anything?) and try to reset it if needed.
Another idea would be, set the TextBox.DataContext to Data, then the text to Patient.naam, in that way, it will be easier for you to debug it, also it will allow efficient DataContext inheritance.

Recommend best approach in this situation

I'm experimenting with MVVM and I can't quite wrap my mind around it.
I have a View (windows) that has several repeated controls.
Let's say I have 4 textbox - button pairs. Their behavior should be the same, after pressing a button paired textbox says "Hello World!"
I have tried several options I could think of:
One ViewModel, 4 string properties binded to textboxes, 1 command
When I bind each button to the same command I can't tell which property needs to be set.
Passing enum to CommandParameter feels awkward.
One ViewModel and UserControl that hosts a textbox and a button repeated 4 times.
Now I need to expose all the properties like Command, CommandParameter, Text etc.. Seems like a lot of work.
Still can't say which property needs to be updated after click.
Each UserControl has a ViewModel
This solves button clicking and setting property, but now I have no clue how to extract texts from nested ViewModels to the window ViewModel.
Is there any other way? I suspect DataTemplates could be of use, but I'm not sure how.
What you describe is such an abstract and contrived idea that it doesn't warrant MVVM. You're talking about TextBoxes and Buttons, which are all 'View', and not the MVVM way of thinking. You'd almost always start with a Model.
There is no 'Model' per-se here though; your specification is literally to set the value of a TextBox on a Button click. The seemingly random list of '4' items (picked out of nowhere) and a seemingly useless TextBox mean nothing.
Putting that aside and assuming that you have a set of 4 business entities, each with a field on them that is user-editable, and an action that the user can trigger, you'd do this:
Create a ViewModel class to represent an item - eg MyItemModel
Create a ViewModel class to represent the set of items (likely it will just return a Collection of the first) - eg AllMyItemsListModel
Then for the View:
Create an ItemsControl, with ItemsSource bound to an instance of the 'collection' of the second ViewModel class
For each ItemTemplate, have a template or UserControl for each item
Within the template or UserControl, bind the TextBox's Text property to the appropriate property of the first class
Bind the Button's Command property to a property on the first class returning an ICommand - using RelayCommand for example
I don't know what you mean about 'extracting texts from nested ViewModels to the window ViewModel' - what does that mean and why would you want to do it?
Hope that helps.
Number 3. Except there would just be one UserControl with viewmodel and then the Main page that would have multiple instances of that UserControl on it. If the main window needs info from the UserControl you could pass it through events or use something like MVVM Light and it's messenger class.
There's no need to create a separate ViewModel for a reusable control that has such simple behavior. Just by adding a few DependencyProperties and an event handler to the simple UserControl you can reuse the logic and only set the properties that are actually different on each instance. For the UserControl XAML you just need to hook up the TextBox to the DependencyProperty and the Button to a Click handler.
<DockPanel>
<Button Content="Reset" Click="Button_Click"/>
<TextBox Text="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type UserControl}}, Path=Text}"/>
</DockPanel>
The code for the UserControl just needs to define the properties that can be bound externally and the handler to reset the Text.
public partial class ResetTextBox : UserControl
{
public static readonly DependencyProperty TextProperty = DependencyProperty.Register(
"Text",
typeof(string),
typeof(ResetTextBox),
new FrameworkPropertyMetadata(null, FrameworkPropertyMetadataOptions.BindsTwoWayByDefault));
public string Text
{
get { return (string)GetValue(TextProperty); }
set { SetValue(TextProperty, value); }
}
public static readonly DependencyProperty ResetTextProperty = DependencyProperty.Register(
"ResetText",
typeof(string),
typeof(ResetTextBox),
new UIPropertyMetadata(String.Empty));
public string ResetText
{
get { return (string)GetValue(ResetTextProperty); }
set { SetValue(ResetTextProperty, value); }
}
public ResetTextBox()
{
InitializeComponent();
}
private void Button_Click(object sender, RoutedEventArgs e)
{
Text = ResetText;
}
}
Then to use the control you just need to bind to your ViewModel string properties and set the default text that should be applied on a reset which can either be hardcoded here, or bound to other VM properties.
<StackPanel>
<local:ResetTextBox ResetText="One" Text="{Binding Name1}"/>
<local:ResetTextBox ResetText="Two" Text="{Binding Name2}"/>
<local:ResetTextBox ResetText="Three" Text="{Binding Name3}"/>
</StackPanel>

Resources