I am creating a user control (a textbox that only accepts integers). The control has to have properties to specify max/min values and whether to allow negative values etc.). I am using MVVM, in my view I have public properties e.g.
const string EXAMPLE = "Example";
string example;
public string Example
{
get { return example; }
set
{
if (value == example) return;
example = value;
OnPropertyChanged(EXAMPLE);
}
}
These properties are in my View so that someone using the control will be able to easily set them. In my ViewModel I have an identical property, I need these properties to be bound together so that they and their backing fields always have the same value. I hate the code repetition too.
To be honest the whole approach feels wrong and usually that is a good indication that I am approaching the whole thing from the wrong direction or misunderstanding something fundamental.
I have used WPF before but this is a first attempt at a custom control.
The first thing I want to make sure is that you're truly trying to make a CustomControl and not a UserControl. I believe this question basically is the same as yours except worded differently.
A UserControl lends itself to the MVVM pattern way more readily than a CustomControl because you would have a .xaml (and .xaml.cs) file along with a .cs file to serve as the ViewModel. On the other hand, a CustomControl is never done with MVVM, as the visual appearance (view) is defined and overridable via a ControlTemplate.
Since you said you have a View and ViewModel, let's think about how you would achieve the behavior you want with your textbox. Your textbox will have to validate and reject user input outside the range of values you desire. This means your View code-behind has to have properties and logic that control the restrictions in the input values of the textbox defined in your View. You have already violated MVVM here.
When you said you have a View, that makes me think you're writing a UserControl. But your requirements (a custom behavior for textbox) suggest that you really need a CustomControl, for which you do not use MVVM.
If you agree that you need a CustomControl, here's a quick and dirty example:
public class RestrictedTextBox : TextBox
{
public static readonly DependencyProperty MaxValueProperty = DependencyProperty.Register("MaxValue", typeof(int), typeof(RestrictedTextBox), new PropertyMetadata(int.MaxValue));
public RestrictedTextBox()
{
PreviewTextInput += RestrictedTextBox_PreviewTextInput;
}
public int MaxValue
{
get
{
return (int)GetValue(MaxValueProperty);
}
set
{
SetValue(MaxValueProperty, value);
}
}
private void RestrictedTextBox_PreviewTextInput(object sender, TextCompositionEventArgs e)
{
int inputDigits;
RestrictedTextBox box = sender as RestrictedTextBox;
if (box != null)
{
if (!e.Text.All(Char.IsDigit))
{
// Stops the text from being handled
e.Handled = true;
}
else if (int.TryParse(box.Text + e.Text, out inputDigits))
{
if (inputDigits > MaxValue)
e.Handled = true;
}
}
}
}
XAML Usage:
<local:RestrictedTextBox MaxValue="100"></local:RestrictedTextBox>
There is no MVVM in a Custom Control.
Any Control is only in the View layer. So what you need to do is expose relevant DP to a consumer that you have no knowledge of.
In the Custom Control you need to define your Control behavior, how it reacts to change in DP value and what should be available to a consumer. In the default Template you define how you want to display this Control.
The consumer may want to set or get some dp values so he'll have to bind your Custom Control'dp to a property in his ViewModel but that's up to him.
Related
I'm currently working on refactoring a lot of duplicated code in a couple of UserControls in Windows Forms project.
The architecture leaves much to be desired with a lot of logic implemented inside the UI layer. This will be fixed eventually, I'm now working on improving what I have on my hands now.
The problem is, that a lot of duplicated code relates directly to controls, for instance:
private void InitDestinationPathControls(string path)
{
if (someField)
{
tbOne.Enabled = false;
tbOne.Visible = false;
btnTwo.Enabled = false;
btnTwo.Visible = false;
tbOne.Text = string.Empty;
return;
}
// (...)
}
Don't get too attached to the cited code itself, it is just an example.
I'd like to move this code to a common base class, but it relies directly on specific fields (even though they are exactly the same in all controls too). Those fields, on the other hand, are generated by the designer, so I cannot extract them to the base class.
The only thing that comes to my mind is to pass those fields as parameters to a method in base class, but then if some method uses a lot of them, I'll end up with a monstrous interface part and that won't actually improve the readability too much.
How can I deal with such common parts of user controls in Windows Forms?
Apparently you have a combination of several controls that appears in several forms. In other words: you have for instance some buttons, comboboxes, etc, that you want to show on different forms, and you want them to have the same behaviour.
If not only the behaviour of these controls is the same on all forms, but also the layout, then consider to create a class derived from UserControl.
The UserControl hides from the outside world which controls are shown, how they are visualized and how they behave.
I assume that you already pulled the model out of the visualisation of the data.
If all instances of this user control should all call the same functions of possibly a different object of the same class, then give your special user control a property that represents this class, or at least an interface. During construction you can plug in the actual class that should handle the actions after operator input.
If, on the other hand, the layout differs on each form, but the collection of buttons, comboboxes, etc and their behaviour is similar on all forms that show this collection of controls and they have a lot of common behaviour, consider to create your own ControlCollection.
For instance, if on several forms you have a button to select a (text) file, labels with the name, size and creation date of the selected file, and an edit box that shows the content of the text file, but you want to layout them differently, consider something like this:
class FileDisplayControls : IDisposable
{
public Button ButtonSelectFile {get;} = new Button();
public Label labelFileName {get; } = new Label();
public Label labelFileSize {get; } = new Label();
public TextBox textFileContents {get; } = new FileContents();
private void ButtonSelectFile_Clicked(object sender, ...)
{
// TODO: open file dialog, display result in labels and text box
}
}
Constructor can set initial layout properties of the controls, and subscribe to events, such that the controls will react on user input.
The user of the class (= code, not operator) immediately has a collection of controls that have some standard behaviour, like react on button click. All he has to do is set the location of the items in his own form. If desired change other layout properties (colour, background) and put them on his own form.
If you want to prevent that others change other visual aspects of the controls than the position, don't publish the control themselves, only the position of the control:
public System.Drawing.Point LocationSelectFileButton
{
get => this.buttonSelectFile.Location;
set => this.buttonSelectFile.Location = value;
}
public System.Drawing.Point LocationFileContentTextBox
{
get => this.textBoxFileContent.Location;
set => this.textBoxFileContent.Location = value;
}
etc.
If needed, you can add events for users:
public event EventHandler SelectedFileChanged;
public string FileName => this.labelFileName.Text;
public string FileContents => this.textBoxFileContent.Text;
etc.
Conclusion
The solution that you choose depends on the similarity between the controls on the various forms:
if Behaviour and Layout are all the same: UserControl
If only position and a few properties different: special class with the properties that are different. This way you can force a more similar style: all "Select File" buttons look similar.
If only one or two behaviours are different: add Action<...> properties or events
If you want full control of the layout: expose the Controls.
The behaviour that is common for all you forms that show these controls (in my example: how to select a file and what to do when a file is selected) is inside the class.
repeated code can be extracted to method (possibly in base class, or as static method in helper class)
public void DisableControls(params Control[] controls)
{
foreach(var c in Controls)
{
c.Enabled = false;
c.Visible = false;
if (c is TextBox t)
{
t.Text = string.Empty;
}
}
}
private void InitDestinationPathControls(string path)
{
if (someField)
{
DisableControls(tbOne, btnTwo);
return;
}
// (...)
}
I am developing a text editor with WPF which should also be usable for blind people.
For screenreaders to read the program interface, the Helptext property must be set for components.
In XAML, this is how it works:
AutomationProperties.HelpText = "Here the accessibility of the editor can be activated or deactivated"
But how do I set the property HelpText in C#?
Attached properties conventionally have a static method that is the property name prefixed with "Set" to update from code. In this case
SetHelpText(DependencyObject, value)
AutomationProperties.SetHelpText(ChkBarrierefrei, "Here the accessibility of the editor can be activated or deactivated")
As you've asked for an example on how to use bindings to achieve your goal, here is a simple one.
We first need to create a class that works as our base class for every so-called ViewModel.
public class ViewModelBase : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void RaisePropertyChanged([CallerMemberName] string propertyName)
{
this.PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
We can now derive specific ViewModels from that class, namely the one we want to use to hold the BarriereFrei Help Text.
public class MyViewModel : ViewModelBase
{
private string barriereFreiHelpText = "Here the accessibility of the editor can be activated or deactivated.";
public string BarriereFreiHelpText
{
get { return this.barriereFreiHelpText; }
set
{
if (value == this.barriereFreiHelpText)
{
return;
}
this.barriereFreiHelpText = value;
this.RaisePropertyChanged(); // This line makes sure the UI is updated
// whenever a new help text is set.
}
}
}
In the View (I assumed it's called MainWindow) we can now use the previously defined ViewModel like shown below:
<Window x:Class="MyProject.Views.MainWindow"
xmlns:viewModel="clr-namespace:MyProject.ViewModels">
<Window.DataContext>
<viewModel:MyViewModel />
</Window.DataContext>
<Grid>
<CheckBox x:Name="ChkBarrierefrei"
Content="Editor ba_rrierefrei"
ToolTip="Hier kann die Barrierefreiheit des Editors aktiviert oder deaktiviert werden"
AutomationProperties.HelpText="{Binding BarriereFreiHelpText}"
Margin="10"
Click="ChkBarrierefrei_CheckedChanged"/>
</Grid>
</Window>
Did you notice the
AutomationProperties.HelpText="{Binding BarriereFreiHelpText}"
part? This is where the magic happens. What this line does is it ensures that the HelpText property is always equal to what you set in the ViewModels BarriereFreiHelpText property.
To make this all work correctly, somewhere in your application you need to wire up the ViewModel and the Window like this:
MyViewModel viewModel = new MyViewModel();
MainWindow window = new MainWindow();
window.DataContext = viewModel;
and you can now change the help text at runtime using
(window.DataContext as MyViewModel).BarriereFreiHelpText = "Whatever you want it to be";
and the UI is automatically updated with the new help text.
This is definitely a better solution than setting the CheckBoxes AutomationProperties.HelpText property directly, as it is much cleaner and less tightly coupled (you are changing the property of a ViewModel without knowing about any UI).
I understand that this all might be overwhelming, but I strongly recommend you follow the so-called MVVM approach, not only for the CheckBoxes help text, but for everything at all. You will save yourself much time, lines of code and nasty bugs.
Further reading:
MVVM und WPF (german) explains how to use MVVM and is way more detailed and easier to understand than my answer,
The World's Simplest C# WPF MVVM Example is a very nice, short and comprehendible article covering everything you need to know in order to create robust and reusable WPF applications,
MVVM Step by Step from Basic to Advanced another nice article, covering why MVVM makes sense.
I hope it's not to late to go with the MVVM approach, and I wish you lots of success and fun with MVVM - it really is nice and fun once you got a hang of it, believe me ;)
I have a user control written in C# & WPF using the MVVM pattern.
All I want to do is have a property in the bound ViewModel exposed to outside of the control. I want to be able to bind to it and I want any changes to the property to be picked up by anything outside the control that is bound to the exposed value.
This sounds simple, but its making me pull out my hair (and there is not much of that left).
I have a dependency property in the user control. The ViewModel has the property implementing the INotifyPropertyChanged interface and is calling the PropertyChanged event correctly.
Some questions:
1) How do I pick up the changes to the ViewModel Property and tie it to the Dependency Property without breaking the MVVM separation? So far the only way I've managed to do this is to assign the ViewModels PropertyChanged Event in the Controls code behind, which is definitely not MVVM.
2) Using the above fudge, I can get the Dependency property to kick off its PropertyChangedCallback, but anything bound to it outside the control does not pick up the change.
There has to be a simple way to do all of this. Note that I've not posted any code here - I'm hoping not to influence the answers with my existing code. Also, you'd probably all laugh at it anyway...
Rob
OK, to clarify - code examples:
usercontrol code behind:
public static DependencyProperty NewRepositoryRunProperty = DependencyProperty.Register("NewRepositoryRun", typeof(int?), typeof(GroupTree),
new FrameworkPropertyMetadata( null, new PropertyChangedCallback(OnNewRepositoryRunChanged)));
public int? NewRepositoryRun
{
get { return (int?)GetValue(NewRepositoryRunProperty); }
set
{
SetValue(NewRepositoryRunProperty, value);
}
}
private static void OnNewRepositoryRunChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
if (e.OldValue != e.NewValue)
{
}
}
public GroupTree()
{
InitializeComponent();
GroupTreeVM vm = new GroupTreeVM();
this.DataContext = vm;
}
Viewmodel (GroupTreeVM.cs)
private int? _NewRepositoryRun;
public int? NewRepositoryRun
{
get
{
return _NewRepositoryRun;
}
set
{
_NewRepositoryRun = value;
NotifyPropertyChanged();
}
}
And now for my weekly "don't do that" answer...
Creating a ViewModel for your UserControl is a code smell.
You're experiencing this issue because of that smell, and it should be an indication that you're doing something wrong.
The solution is to ditch the VM built for the UserControl. If it contains business logic, it should be moved to an appropriate location in another ViewModel.
You should think of a UserControl as nothing more than a more complex control. Does the TextBox have its own ViewModel? No. You bind your VM's property to the Text property of the control, and the control shows your text in its UI.
Think of UserControls in MVVM like this--For each model, you have a UserControl, and it is designed to present the data in that model to the user. You can use it anywhere you want to show the user that model. Does it need a button? Expose an ICommand property on your UserControl and let your business logic bind to it. Does your business logic need to know something going on inside? Add a routed event.
Normally, in WPF, if you find yourself asking why it hurts to do something, it's because you shouldn't do it.
Perhaps I've misunderstood, but it seems like you're trying to use binding in the code behind?
public MyUserControl()
{
InitializeComponent();
// Set your datacontext.
var binding = new Binding("SomeVMProperty");
binding.Source = this.DataContext;
SetBinding(MyDependencyProperty, binding);
}
I'm trying to do set a DynamicResource on a content of a Attached Property but this isn't working. Trying to understand why, but I can't figure it out.
Basically, I'm implementing a watermark on a textbox using the code available at: Watermark / hint text / placeholder TextBox in WPF
provided by John Myczek
and using it as so:
<TextBox Text="{Binding SomeProperty}">
<helper:WatermarkService.Watermark>
<TextBlock FontStyle="Italic" Text="{DynamicResource SomeResource}" />
</helper:WatermarkService.Watermark>
</TextBox>
the inner TextBlock works just fine if it is outside the WatermarkService.Watermark attached property. The SomeResource for some reason is empty.
My resources are being loaded as so this.Resources.MergedDictionaries.Add(lastDictionary); since the app is localized and the data is being retrieved from a central place.
Do the controls on the attached properties share the same resources set as their "parents"? What is wrong here?
Thank you
The problem is clear. Dynamic resources are resolved by parsing upwards the logical tree. The dynamic resource is not found because your textblock is not in the correct logical tree, probably he does not have a logical parent and that is why the resource is not found.
You could solve it by adding it to the correct logical tree like for example it could be the child of the textbox. It is not so trivial and depends also on the usage that is required, because the customization of the logical tree is not so trivial.
There is not so simple like having a public method AddLogicalChild because then you would mess up the entire system. Now the question is who has the responsibility of doing this. The general solution could be to have a custom TextBox that overrides logical children related methods and returns also the watermark textblock.
It is not the global solution but in your case you could have a custom textbox overriding the LogicalChildren property like this:
public class WaterTextBox : TextBox
{
protected override IEnumerator LogicalChildren
{
get
{
ArrayList list = new ArrayList();
list.Add(WatermarkService.GetWatermark(this));
return (IEnumerator)list.GetEnumerator();
}
}
}
Remember this is just a workaround and this way would work only on your custom textboxes with dynamic resources.
Also it is not the correct implementation because you should add the watermark to the other logical children not ignore the other logical children and have only the watermark which is not even checked for null like this:
public class WaterTextBox : TextBox
{
protected override IEnumerator LogicalChildren
{
get
{
ArrayList list = new ArrayList();
IEnumerator enumerator = base.LogicalChildren;
while (enumerator.MoveNext())
{
list.Add(enumerator.Current);
}
object watermark = WatermarkService.GetWatermark(this);
if (watermark != null && !list.Contains(watermark))
{
list.Add(WatermarkService.GetWatermark(this));
}
return (IEnumerator)list.GetEnumerator();
}
}
}
To make it more general you should define an interface like IWatermark defining a property like IsWaterMarkAdded which will be implemented by your custom TextBox and ComboBox and will be used by the watermark service. The LogicalChildren override will check for the value of this property. This way you can extend functionality for your TextBox and ComboBox but still it is not an extensible solution for any control.
I have a simple Converter that adds a "+" symbol to a positive number that is entered in a TextBox. When the number is entered I want to initiate some action, but I don't want to wait until the TextBox loses focus: I want to update the binding immediately as the user enters the text.
The default behaviour of a TextBox is that when a user navigates away from the box, the binding source is updated (UpdateSourceTrigger=LostFocus). In this scenario, my converter works as expected, and the + is added. However, when I change it to the following, the + is never added.
<TextBox Text="{Binding Value, Converter={StaticResource PlusConverter}, UpdateSourceTrigger=PropertyChanged}" />
I can imagine that there is a good reason why my converter.Convert() method is not called. I would like to have the following behaviour:
When the users enters text, the source is updated immediately
When the TextBox loses focus, the + is added.
Now I'm looking for a nice, but especially GENERIC way of doing this (as I have a lot of these TextBoxes in my app).
So far I haven't been able to come up with a proper solution.
Agree w/Kent B, you need to post your Converter code.
I've been able to get part 1 to work with a simple converter (I'm binding a second unconverted TextBlock to show that the value is indeed getting updated).
However, if I understand your part 2, you're trying to get the TextBox's text to update with a "+" after it loses focus. This is a little trickier and I don't think you'll be able to do it with just an IConverter. If it can be done, I'd love to know the answer as well.
What you're essentially asking for is watermarked input behavior e.g. allow a user to enter some data, and have it get formatted correctly (both in the underlying DataBinding and in the UI). The quickest/dirtiest solution to this is to handle the TextBoxes' LostFocus but since you're using that all over your app, this may not be feasible.
You could also consider wrapping the TextBox in your own UserControl. If you look at WPFToolkit's implementation of a DatePicker it has similar behavior: allow the user to enter free form text, then auto-convert the value to a DateTime (if valid) and show the DateTime in a localized format.
HTH.
The other thing you might want to do, is edit the template for TextBox and move the actual PART_ContentHost to the right a bit, then have a TextBlock indicate the +/- part; i.e. change the template of the TextBox from:
Control
- Border
-- PART_ContentHost (the actual editing part)
into:
Control
- Border
-- Horizontal StackPanel
--- TextBlock (contains +/- sign, has 2px right margin)
--- PART_ContentHost (actual editable section)
Then, bind the TextBlock's content to the text, but with a converter that either writes a '+' or '-'. This way, the user can't delete the +/- part, and you don't have to worry about parsing it; this also makes it easier if you want to do something like make the negative sign red or something.
Thanks for your answers! I looked into this issue myself a bit futher and came up with the following solution (which I'm not entirely satisfied with, but it works fine)
I've created a CustomControl that adds functionality to the TextBox.
It provided an event handler for the LostFocus event
When this event occurs, my converter is called.
However, the way I resolve the Converter is not very satisfying (I take it from the Style that is associated with my TextBox). The Style has a Setter for the Text property. From that setter I can access my Converter.
Of course I could also make a "PlusTextBox", but I use different converters and I wanted a generic solution.
public class TextBoxEx : TextBox
{
public TextBoxEx()
{
AddHandler(LostFocusEvent,
new RoutedEventHandler(CallConverter), true);
}
public Type SourceType
{
get { return (Type)GetValue(SourceTypeProperty); }
set { SetValue(SourceTypeProperty, value); }
}
// Using a DependencyProperty as the backing store for SourceType. This enables animation, styling, binding, etc...
public static readonly DependencyProperty SourceTypeProperty =
DependencyProperty.Register("SourceType", typeof(Type), typeof(TextBoxEx), new UIPropertyMetadata(null));
private static void CallConverter(object sender, RoutedEventArgs e)
{
TextBoxEx textBoxEx = sender as TextBoxEx;
if (textBoxEx.Style == null) {
return;
}
if (textBoxEx.SourceType == null) {
}
foreach (Setter setter in textBoxEx.Style.Setters) {
if (setter.Property.ToString() == "Text") {
if (! (setter.Value is Binding) ) {
return;
}
Binding binding = setter.Value as Binding;
if (binding.Converter == null) {
return;
}
object value = binding.Converter.ConvertBack(textBoxEx.Text, textBoxEx.SourceType, binding.ConverterParameter, System.Globalization.CultureInfo.CurrentCulture);
value = binding.Converter.Convert(value, typeof(string), binding.ConverterParameter, System.Globalization.CultureInfo.CurrentCulture);
if (!(value is string)) {
return;
}
textBoxEx.Text = (string)value;
}
}
}
}
In your converter, couldn't you just check the keyboard's current focus? Something like:
TextBox focusedTextBox = Keyboard.FocusedElement as TextBox;
if (focusedTextBox == null || focusedTextBox != senderOrWhatever)
{
' this is where you'd add the +, because the textbox doesn't have focus.
}