Skip to main content
edited markup
Source Link
Mathieu Guindon
  • 75.1k
  • 18
  • 191
  • 463

Grid, GroupBox, WrapPanel, margins, padding and styles make a prettier UIGrid, GroupBox, WrapPanel, ScrollViewer, margins, padding and styles make a prettier UI

WrapPanel

<Window.Resources>
    <Style x:Key="InputField" TargetType="{x:Type TextBox}">
        <Setter Property="MinWidth" Value="172" />
        <Setter Property="Foreground" Value="Blue" />
    </Style>
    <Style x:Key="Heading" TargetType="{x:Type TextBlock}">
        <Setter Property="FontWeight" Value="Bold" />
    </Style>
</Window.Resources>
<Grid Margin="4">

    <Grid.RowDefinitions>
        <RowDefinition Height="Auto" />
        <RowDefinition Height="*" />
        <RowDefinition Height="32" />
    </Grid.RowDefinitions>

    <!-- search box -->
    
    <GroupBox Grid.Row="0" Padding="4">
        <GroupBox.Header>
      Padding="4"      <TextBlock Style="{StaticResource Heading}" 
              Header="Work         Text="Work Order Name">Name" />
        </GroupBox.Header>
        
        <StackPanel Orientation="Horizontal">
                        
            <TextBox Text="{Binding WorkOrderName}"
                     HorizontalAlignment="Left"
                     Width="172" Margin="2"
                     AcceptsReturn="False">
                <TextBox.InputBindings>
                    <KeyBinding Key="Enter" Command="{Binding GetWorkOrderCommand}" />
                </TextBox.InputBindings>
            </TextBox>

            <Button Command="{Binding GetWorkOrderCommand}" 
                    Margin="2" 
                    Padding="2">
                <Button.Content>
                    <StackPanel Orientation="Horizontal">
                        <Image Source="Images/find.png" />
                      <!--  <TextBlock Text="Search" /TextBox> -->
                    </StackPanel>
                </Button.InputBindings>Content>
            </TextBox>Button>

        </StackPanel>

    </GroupBox>

    <!-- form -->
    
    <GroupBox Grid.Row="1" Padding="2" Margin="2"
              Padding="4"IsEnabled="{Binding IsNewWorkOrder}">
        <GroupBox.Header>
      Header="Work      <TextBlock Style="{StaticResource Heading}" 
                       Text="Work Order Info" />
        </GroupBox.Header>

      IsEnabled="{Binding IsNewWorkOrder}"> <ScrollViewer VerticalScrollBarVisibility="Auto">

            <WrapPanel DataContext="{Binding SelectedWorkOrder}">
                <StackPanel Margin="2">
                    <TextBlock Text="Part Number"/>
                    <TextBox Style="{StaticResource InputField}" 
                             Text="{Binding PartNumber}"/>
                </StackPanel>
                <StackPanel Margin="2">
                    <TextBlock Text="Created"/>
                    <TextBox Style="{StaticResource InputField}" 
                             Text="{Binding Created}"/>
                </StackPanel>
                <StackPanel Margin="2">
                    <TextBlock Text="Closed"/>
                    <TextBox Style="{StaticResource InputField}" 
                             Text="{Binding Closed}"/>
                </StackPanel>
                <StackPanel Margin="2">
                    <TextBlock Text="First Number"/>
                    <TextBox Style="{StaticResource InputField}" 
                             Text="{Binding FirstNumber}"/>
                </StackPanel>
                <StackPanel Margin="2">
                    <TextBlock Text="Last Number"/>
                    <TextBox Style="{StaticResource InputField}" 
                             Text="{Binding LastNumber}"/>
                </StackPanel> 

            </WrapPanel>
        </ScrollViewer>

    </GroupBox>

    <Button<!-- bottom panel-->
    
    <Border Grid.Row="2" Background="WhiteSmoke" />
    
    <WrapPanel Grid.Row="2" 
               HorizontalAlignment="Right">
    
        <Button Margin="4" Padding="2"
            Width="128"
            HorizontalAlignment="Right"
            Content="Save"/>

        <Button Margin="4" Padding="2"
            HorizontalAlignment="Right"
            Content="Close"/>

    </WrapPanel>
    
</Grid>

I noticed the Save button doesn't have a command binding. You may want to fix that ;)

Grid, GroupBox, WrapPanel, margins, padding and styles make a prettier UI

<Grid Margin="4">

    <Grid.RowDefinitions>
        <RowDefinition Height="Auto" />
        <RowDefinition Height="*" />
        <RowDefinition Height="32" />
    </Grid.RowDefinitions>

    <GroupBox Grid.Row="0"
              Padding="4"
              Header="Work Order Name">
        <TextBox Text="{Binding WorkOrderName}"
                 AcceptsReturn="False">
            <TextBox.InputBindings>
                <KeyBinding Key="Enter" 
                            Command="{Binding GetWorkOrderCommand}" />
            </TextBox.InputBindings>
        </TextBox>
    </GroupBox>

    <GroupBox Grid.Row="1" 
              Padding="4"
              Header="Work Order Info" 
              IsEnabled="{Binding IsNewWorkOrder}">

        <WrapPanel DataContext="{Binding SelectedWorkOrder}">
            <StackPanel Margin="2">
                <TextBlock Text="Part Number"/>
                <TextBox Style="{StaticResource InputField}" 
                         Text="{Binding PartNumber}"/>
            </StackPanel>
            <StackPanel Margin="2">
                <TextBlock Text="Created"/>
                <TextBox Style="{StaticResource InputField}" 
                         Text="{Binding Created}"/>
            </StackPanel>
            <StackPanel Margin="2">
                <TextBlock Text="Closed"/>
                <TextBox Style="{StaticResource InputField}" 
                         Text="{Binding Closed}"/>
            </StackPanel>
            <StackPanel Margin="2">
                <TextBlock Text="First Number"/>
                <TextBox Style="{StaticResource InputField}" 
                         Text="{Binding FirstNumber}"/>
            </StackPanel>
            <StackPanel Margin="2">
                <TextBlock Text="Last Number"/>
                <TextBox Style="{StaticResource InputField}" 
                         Text="{Binding LastNumber}"/>
            </StackPanel>
        </WrapPanel>

    </GroupBox>

    <Button Grid.Row="2" Content="Save"/>

</Grid>

Grid, GroupBox, WrapPanel, ScrollViewer, margins, padding and styles make a prettier UI

WrapPanel

<Window.Resources>
    <Style x:Key="InputField" TargetType="{x:Type TextBox}">
        <Setter Property="MinWidth" Value="172" />
        <Setter Property="Foreground" Value="Blue" />
    </Style>
    <Style x:Key="Heading" TargetType="{x:Type TextBlock}">
        <Setter Property="FontWeight" Value="Bold" />
    </Style>
</Window.Resources>
<Grid Margin="4">

    <Grid.RowDefinitions>
        <RowDefinition Height="Auto" />
        <RowDefinition Height="*" />
        <RowDefinition Height="32" />
    </Grid.RowDefinitions>

    <!-- search box -->
    
    <GroupBox Grid.Row="0" Padding="4">
        <GroupBox.Header>
            <TextBlock Style="{StaticResource Heading}" 
                       Text="Work Order Name" />
        </GroupBox.Header>
        
        <StackPanel Orientation="Horizontal">
                        
            <TextBox Text="{Binding WorkOrderName}"
                     HorizontalAlignment="Left"
                     Width="172" Margin="2"
                     AcceptsReturn="False">
                <TextBox.InputBindings>
                    <KeyBinding Key="Enter" Command="{Binding GetWorkOrderCommand}" />
                </TextBox.InputBindings>
            </TextBox>

            <Button Command="{Binding GetWorkOrderCommand}" 
                    Margin="2" 
                    Padding="2">
                <Button.Content>
                    <StackPanel Orientation="Horizontal">
                        <Image Source="Images/find.png" />
                      <!--  <TextBlock Text="Search" /> -->
                    </StackPanel>
                </Button.Content>
            </Button>

        </StackPanel>

    </GroupBox>

    <!-- form -->
    
    <GroupBox Grid.Row="1" Padding="2" Margin="2"
              IsEnabled="{Binding IsNewWorkOrder}">
        <GroupBox.Header>
            <TextBlock Style="{StaticResource Heading}" 
                       Text="Work Order Info" />
        </GroupBox.Header>

        <ScrollViewer VerticalScrollBarVisibility="Auto">

            <WrapPanel DataContext="{Binding SelectedWorkOrder}">
                <StackPanel Margin="2">
                    <TextBlock Text="Part Number"/>
                    <TextBox Style="{StaticResource InputField}"
                             Text="{Binding PartNumber}"/>
                </StackPanel>
                <StackPanel Margin="2">
                    <TextBlock Text="Created"/>
                    <TextBox Style="{StaticResource InputField}" 
                             Text="{Binding Created}"/>
                </StackPanel>
                <StackPanel Margin="2">
                    <TextBlock Text="Closed"/>
                    <TextBox Style="{StaticResource InputField}"
                             Text="{Binding Closed}"/>
                </StackPanel>
                <StackPanel Margin="2">
                    <TextBlock Text="First Number"/>
                    <TextBox Style="{StaticResource InputField}" 
                             Text="{Binding FirstNumber}"/>
                </StackPanel>
                <StackPanel Margin="2">
                    <TextBlock Text="Last Number"/>
                    <TextBox Style="{StaticResource InputField}" 
                             Text="{Binding LastNumber}"/>
                </StackPanel> 

            </WrapPanel>
        </ScrollViewer>

    </GroupBox>

    <!-- bottom panel-->
    
    <Border Grid.Row="2" Background="WhiteSmoke" />
    
    <WrapPanel Grid.Row="2" 
               HorizontalAlignment="Right">
    
        <Button Margin="4" Padding="2"
            Width="128"
            HorizontalAlignment="Right"
            Content="Save"/>

        <Button Margin="4" Padding="2"
            HorizontalAlignment="Right"
            Content="Close"/>

    </WrapPanel>
    
</Grid>

I noticed the Save button doesn't have a command binding. You may want to fix that ;)

Source Link
Mathieu Guindon
  • 75.1k
  • 18
  • 191
  • 463

View

The XAML is rather... simplistic:

designer rendition of XAML code

Each label and textbox (and the Save button) stretches all the way across the window.

Instead of just dumping controls into a StackPanel, you could consider leveraging automagic layout with a WrapPanel, and making each label+textbox duet logically regrouped.

I like that you have no WinForms-like absolute positioning, but I would have expected to see margins, or styles. I'd recommend further nesting in your XAML - use a Grid to define sections, regroup label+textbox fields in their own StackPanel, and use styles to make all fields feel the same, without having to set MinWidth and fonts and other things every time.

Grid, GroupBox, WrapPanel, margins, padding and styles make a prettier UI

Having your controls logically regrouped also visually regroups them, and your user has a better experience. Here I've removed the "Work Order Name" textblock, and replaced it with a GroupBox. Here's the XAML markup for the above screenshot, I'd like to highlight this part:

<WrapPanel DataContext="{Binding SelectedWorkOrder}">

Setting the container's data context allows everything under it to bind to the SelectedWorkOrder properties without having to qualify them, which makes clearer markup.

<Grid Margin="4">

    <Grid.RowDefinitions>
        <RowDefinition Height="Auto" />
        <RowDefinition Height="*" />
        <RowDefinition Height="32" />
    </Grid.RowDefinitions>

    <GroupBox Grid.Row="0"
              Padding="4"
              Header="Work Order Name">
        <TextBox Text="{Binding WorkOrderName}"
                 AcceptsReturn="False">
            <TextBox.InputBindings>
                <KeyBinding Key="Enter" 
                            Command="{Binding GetWorkOrderCommand}" />
            </TextBox.InputBindings>
        </TextBox>
    </GroupBox>

    <GroupBox Grid.Row="1" 
              Padding="4"
              Header="Work Order Info" 
              IsEnabled="{Binding IsNewWorkOrder}">

        <WrapPanel DataContext="{Binding SelectedWorkOrder}">
            <StackPanel Margin="2">
                <TextBlock Text="Part Number"/>
                <TextBox Style="{StaticResource InputField}" 
                         Text="{Binding PartNumber}"/>
            </StackPanel>
            <StackPanel Margin="2">
                <TextBlock Text="Created"/>
                <TextBox Style="{StaticResource InputField}" 
                         Text="{Binding Created}"/>
            </StackPanel>
            <StackPanel Margin="2">
                <TextBlock Text="Closed"/>
                <TextBox Style="{StaticResource InputField}" 
                         Text="{Binding Closed}"/>
            </StackPanel>
            <StackPanel Margin="2">
                <TextBlock Text="First Number"/>
                <TextBox Style="{StaticResource InputField}" 
                         Text="{Binding FirstNumber}"/>
            </StackPanel>
            <StackPanel Margin="2">
                <TextBlock Text="Last Number"/>
                <TextBox Style="{StaticResource InputField}" 
                         Text="{Binding LastNumber}"/>
            </StackPanel>
        </WrapPanel>

    </GroupBox>

    <Button Grid.Row="2" Content="Save"/>

</Grid>

Comments in XAML shouldn't try to explain anything:

<!--This textbox is incharge of searching the work order in the DB. [...]

I hope not. A textbox shouldn't be in charge of anything but collecting user input and displaying a value. This comment is misleading at best, and tries to explain the mechanics of the ViewModel, which has nothing to do with the textbox itself - help yourself, remove it.


I'm not sure why you're passing your command a parameter here:

<TextBox Text="{Binding WorkOrderName}" AcceptsReturn="False">
    <TextBox.InputBindings>
        <KeyBinding Key="Enter" Command="{Binding GetWorkOrderCommand}"
                        CommandParameter="{Binding Path=Text, 
                        RelativeSource={RelativeSource AncestorType=TextBox}}"/>
    </TextBox.InputBindings>
</TextBox>

The ViewModel already knows what the WorkOrderName is, so the command binding can be simplified:

<TextBox Text="{Binding WorkOrderName}" AcceptsReturn="False">
    <TextBox.InputBindings>
        <KeyBinding Key="Enter" Command="{Binding GetWorkOrderCommand}" />
    </TextBox.InputBindings>
</TextBox>

Model

Not much to say about the WorkOrder model, except that I find auto-properties don't really need the whitespace in-between:

public class WorkOrder
{
    public int WorkOrderId { get; set; }
    public string WorkOrderName { get; set; }
    public string PartNumber { get; set; }
    public DateTime Created { get; set; }
    public DateTime? Closed { get; set; }
    public long FirstNumber { get; set; }
    public long LastNumber { get; set; }
}

This class is an entity type - I don't like that the View is binding directly to it. I would create another class, a WorkOrderViewModel, that implements the INotifyPropertyChanged interface - this class only needs to expose the properties of the Model that the View is interested in, and can easily know whether it's a "new" work order:

public class WorkOrderViewModel : INotifyPropertyChanged
{
    private readonly WorkOrder _entity;

    public WorkOrderViewModel()
        : this(new WorkOrder())
    { }

    public WorkOrderViewMode(WorkOrder entity)
    {
        _entity = entity;
    }

    public WorkOrder Model { get { return _entity; } }

    public string WorkOrderName 
    { 
        get { return _entity.WorkOrderName; } 
        set
        {
            _entity.WorkOrderName = value;
            NotifyPropertyChanged("WorkOrderName");
        }
    }

    // ...

    public bool IsNew { get { return _entity.WorkOrderId == 0; } }
}

ViewModel

The WorkOrderManagerViewModel can then expose SelectedWorkOrder as a WorkOrderViewModel.

Comments

Again, the comments in the ViewModel should explain what's going on in the ViewModel, not in the View - this is a waiting-to-become-a-blatant-lie comment:

//bound to the work order name textbox

This one is also not clear:

//on text changed - clear all values

"clearing all values" is the result of setting the SelectedWorkOrder to null... but only because of things that happen outside the ViewModel (specifically, because of the bindings in the View) - the comment would be better if reworded like this:

 // text has changed, invalidate current selection.

This one is also something that the ViewModel has no control over:

//after enter was pressed - used to search if exists or not

If the command bindings change in the View, this commment becomes wrong, or incomplete - what if you added a Search button next to the text box, for convenience? What if more controls were added, with command bindings to this command?

Other than that, I find the name GetWorkOrder a little bit awkward for a method that returns void. I like naming my command methods CanExecuteXxxxx and ExecuteXxxxx - so that would be CanExecuteGetWorkOrder and ExecuteGetWorkOrder - these names also help justifying the object parameter that the ICommand interface wants.


The CanSave logic could be simplified:

    if (SelectedWorkOrder == null)
        return false;

    return true;

Is really nothing more than return SelectedWorkOrder != null;.