Click here to Skip to main content
15,886,137 members
Please Sign up or sign in to vote.
0.00/5 (No votes)
I have a WPF app where I am trying to create an on-screen keyboard as a Usercontrol.

Here is the XAML for the design:

XML
<UserControl x:Class="MyDiskTools.UserControls.NodeGrid.NodeGrid"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
             xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
             xmlns:local="clr-namespace:MyDiskTools.UserControls.NodeGrid"
             mc:Ignorable="d">
    <Grid>
        <Grid.Resources>
            <Style TargetType="Button">
                <Setter Property="Padding" Value="{Binding ButtonPadding, FallbackValue=5}"/>
                <Setter Property="BorderThickness" Value="{Binding ButtonBorderThickness, FallbackValue=1}"/>
                <Setter Property="Command" Value="{Binding InputCharacterCommand}"/>
                <Setter Property="CommandParameter" Value="{Binding Path=Content, RelativeSource={RelativeSource Self}}"/>
                <Style.Triggers>
                    <Trigger Property="IsMouseOver" Value="True">
                        <Setter Property="BorderThickness" Value="{Binding HighlightBorderThickness, FallbackValue=5}"/>
                        <Setter Property="FontSize" Value="{Binding HighlightFontSize, FallbackValue=20}"/>                        
                    </Trigger>
                </Style.Triggers>
            </Style>            
        </Grid.Resources>
        <Grid.RowDefinitions>
            <RowDefinition></RowDefinition>
            <RowDefinition></RowDefinition>
            <RowDefinition></RowDefinition>
            <RowDefinition></RowDefinition>
            <RowDefinition></RowDefinition>
        </Grid.RowDefinitions>
        <UniformGrid Grid.Row="0" Rows="1">
            <Button Content="A" />
            <Button Content="B" />
            <Button Content="C" />
            <Button Content="D" />
            <Button Content="E" />
            <Button Content="F" />
        </UniformGrid>
        <UniformGrid Grid.Row="1" Rows="1">
            <Button Content="G" />
            <Button Content="H" />
            <Button Content="I" />
            <Button Content="J" />
            <Button Content="K" />
            <Button Content="L" />
            <Button Content="M" />
        </UniformGrid>
        <UniformGrid Grid.Row="2" Rows="1">
            <Button Content="N" />
            <Button Content="O" />
            <Button Content="P" />
            <Button Content="Q" />
            <Button Content="R" />
            <Button Content="S" />
            <Button Content="T" />
        </UniformGrid>
        <UniformGrid Grid.Row="3" Rows="1">
            <Button Content="U" />
            <Button Content="V" />
            <Button Content="W" />
            <Button Content="X" />
            <Button Content="Y" />
            <Button Content="Z" />
        </UniformGrid>
        <TextBox Name="InputMessage"  IsEnabled="False" Background="Beige" Grid.Row="4" Text="{Binding PasswordDisplay, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"/>
    </Grid>
</UserControl>


The code-behind is here:

C++
public partial class NodeGrid : UserControl
    {
        public NodeGrid()
        {
            InitializeComponent();
            DataContext = new NodeGridVM();
        }

        public string Message
        {
            get
            {
                return InputMessage.Text;
            }
        }

        #region Dependency Properties
        [TypeConverter(typeof(LengthConverter))]

        
        public double InternalPadding
        {
            get { return (double)GetValue(InternalPaddingProperty); }
            set { SetValue(InternalPaddingProperty, value); }
        }

        // Using a DependencyProperty as the backing store for InternalPadding.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty InternalPaddingProperty =
            DependencyProperty.Register("InternalPadding", typeof(double), typeof(NodeGrid), new PropertyMetadata(double.NaN));

        public double ButtonHeight
        {
            get { return (double)GetValue(ButtonHeightProperty); }
            set { SetValue(ButtonHeightProperty, value); }
        }

        // Using a DependencyProperty as the backing store for ButtonHeight.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty ButtonHeightProperty =
            DependencyProperty.Register("ButtonHeight", typeof(double), typeof(NodeGrid), new PropertyMetadata(double.NaN));
        #endregion
    }


... and the ViewModel is:

C#
class NodeGridVM : INotifyPropertyChanged
    {
        private string _passwordDisplay = "";

        public string PasswordDisplay
        {
            get
            {
                return _passwordDisplay;
            }

            set
            {
                if (value != _passwordDisplay)
                {
                    _passwordDisplay = value;
                    OnPropertyChange();
                }
            }
        }

        private ICommand _inputCharacterCommand;

        public ICommand InputCharacterCommand
        {
            get
            {
                if (_inputCharacterCommand == null)
                {
                    InputCharacterCommand = new InputCharacterCommand(this);
                }
                    
                return _inputCharacterCommand;
            }

            set
            {
                _inputCharacterCommand = value;
            }
        }
        public void AddCharacter(string input)
        {
            if (input != null)
            {
                PasswordDisplay = String.Concat(PasswordDisplay, input);
            }            
        }

        private void OnPropertyChange([CallerMemberName] string property = null)
        {
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(property));
        }

        public event PropertyChangedEventHandler PropertyChanged;

        public bool InputAllowed()
        {
            if (PasswordDisplay == null)
            {
                return true;
            }

            if (PasswordDisplay.Length > 20)
            {
                return false;
            }

            return true;
        }
    }

    class InputCharacterCommand : ICommand
    {
        private NodeGridVM _viewmodel;

        public InputCharacterCommand(NodeGridVM vm)
        {
            _viewmodel = vm;
        }

        public event EventHandler CanExecuteChanged;
        

        public bool CanExecute(object parameter)
        {
            return (_viewmodel.InputAllowed());
        }

        public void Execute(object parameter)
        {
            _viewmodel.AddCharacter(parameter as String);
        }
    }


Please do note that this is just a sketch-up of the code, and more dependency properties will be created for future use.

What I have tried:

However, according to most of the opinions online, giving a Usercontrol a viewmodel of its own is an extremely bad idea. Should I, then, scrap the ViewModel and just put all the logic in the code-behind file?
Posted
Updated 23-Feb-17 10:03am
v3
Comments
[no name] 23-Feb-17 15:54pm    
"according to most of the opinions online, giving a Usercontrol a viewmodel of its own is an extremely bad idea".... according to what sources?
Sabyasachi Mukherjee 23-Feb-17 16:03pm    
Source 1:
http://stackoverflow.com/questions/1939345/wpf-should-a-user-control-have-its-own-viewmodel
[no name] 23-Feb-17 16:09pm    
So ONE person says it's a bad idea and you interpret that to be gospel? I don't think it's a bad idea at all. It depends on the application.
Sabyasachi Mukherjee 23-Feb-17 16:13pm    
Generally speaking, is it a good practice to give a Usercontrol its own VM? Or does it depend entirely on the specific instances?
[no name] 23-Feb-17 16:26pm    
Again, it depends.

1 solution

Quote:
according to most of the opinions online, giving a Usercontrol a viewmodel of its own is an extremely bad idea
This is a new one for me. Popular opinion is actually the complete opposite! To me, it is personal preference or usage-specific.

For example, I may have a complex entry form with a lot of Xaml. The form has the VM (ViewModel), but regions of the form are broken out into separate UCs (UserControls).

Another example code be that each region on a form has a specific task, so the form is just a container with no VM, and each region has its own function, so there are multiple UCs each with their own VMs.

The easiest framework rule to remember is UI code belongs in the code-behind, logic in the VM.

An on-screen keyboard is something that is re-usable. So it should be code so that it can be used in any input control: eg: any TextBox on a window/control.

Hope this helps!
 
Share this answer
 
Comments
Sabyasachi Mukherjee 23-Feb-17 16:14pm    
Thank you for clarifying the issue. I was happy with my UserControl until I read the opinions on UserControl's own VMs today.
Graeme_Grant 23-Feb-17 16:20pm    
There are those who think they know what they are talking about and there are those who know what they are talking about... ;)
[no name] 23-Feb-17 16:27pm    
And those that think they know what they are talking about really p$ss off those of us that actually do. :-)
Ludovic Feltz 18-Feb-20 10:50am    
And those who DON'T know what they are NOT talking about and try to learn from those that think they know and those who actually do (but have hard time trying do figure out which one is the one who really know...) ;-) And finally learn that both solution are usable, depends on what you want to do....

This content, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)



CodeProject, 20 Bay Street, 11th Floor Toronto, Ontario, Canada M5J 2N8 +1 (416) 849-8900