Search code examples
wpfexpression-blend

How to create a custom control to resemble an electronic breadboard using WPF?


A breadboard is a tool that is used to implement simple electronic circuits, it is a set of nodes used to put ICs on them and connect between them using wires to build the circuit. Wikipedia

What I want to do is to make a user control in WPF to be used in a larger application that simulates electronic circuits.

The user has to place ICs on that breadboard.

The first approach I used is to make the user control with a background image of a breadboard, inside it is a layout container ex: StackPanel (To stack ICs horizontally) with height that covers the central part of the image.

and then I made another user control that resembles an IC to be added to the former container, I drew the pins and a black rectangle.

Specifically what I want to do now is to make the size of the IC relative to that of the breadboard. meaning that when the size of the breadboard expands or shrinks the IC will remain in the same points it was.

This is the XAML code of the Breadboard:

<UserControl
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:Breadboard"
mc:Ignorable="d"
x:Class="Breadboard.MainControl"
x:Name="UserControl" d:DesignWidth="91.583" Width="Auto" d:DesignHeight="26.501" Padding="0">
<UserControl.Background>
    <ImageBrush ImageSource="Images/breadboard.png" Stretch="Uniform"/>
</UserControl.Background>

<StackPanel x:Name="LayoutRoot" Orientation="Horizontal" VerticalAlignment="Center" Margin="0" Height="8.835">
    <StackPanel.Background>
        <ImageBrush/>
    </StackPanel.Background>
    <local:IC Margin="0" Width="Auto"/>
</StackPanel>

This is the XAML code of the IC:

<Viewbox Stretch="Fill">

    <StackPanel x:Name="LayoutRoot" Height="40" Width="52.833">
        <StackPanel x:Name="UpperPins" HorizontalAlignment="Left" Orientation="Horizontal">
            <Image Source="Images/pin.png" Stretch="Fill" Width="7" Margin="3,0,0,0"/>
            <Image Source="Images/pin.png" Stretch="Fill" Width="7" Margin="6,0,0,0"/>
            <Image Source="Images/pin.png" Stretch="Fill" Width="7" Margin="6,0,0,0"/>
            <Image Source="Images/pin.png" Stretch="Fill" Width="7" Margin="6,0,3,0"/>
        </StackPanel>
        <Rectangle Fill="Black" Height="36"/>
        <StackPanel x:Name="LowerPins" HorizontalAlignment="Left" Orientation="Horizontal" RenderTransformOrigin="0.5,0.5">
            <StackPanel.RenderTransform>
                <TransformGroup>
                    <ScaleTransform/>
                    <SkewTransform/>
                    <RotateTransform Angle="180"/>
                    <TranslateTransform/>
                </TransformGroup>
            </StackPanel.RenderTransform>
            <Image Source="Images/pin.png" Stretch="Fill" Width="7" Margin="3,0,0,0"/>
            <Image Source="Images/pin.png" Stretch="Fill" Width="7" Margin="6,0,0,0"/>
            <Image Source="Images/pin.png" Stretch="Fill" Width="7" Margin="6,0,0,0"/>
            <Image Source="Images/pin.png" Stretch="Fill" Width="7" Margin="6,0,3,0"/>
        </StackPanel>
    </StackPanel>
</Viewbox>

This is the C# code of the IC (The Constructor):

namespace Breadboard{
public partial class IC : UserControl
{
    public IC(int pins)
    {
        if (pins % 2 != 0) throw new OddNumberOfPins();
        this.InitializeComponent();
        Width = (pins/2)*(6 + 7);
        UpperPins.Children.Clear();
        LowerPins.Children.Clear();
        UpperPins.Children.Add(new Image() { Source = new BitmapImage(new Uri(@"Images/pin.png",UriKind.Relative)), Stretch = Stretch.Fill, Width = 7, Margin = new Thickness(3, 0, 0, 0) });
        LowerPins.Children.Add(new Image() { Source = new BitmapImage(new Uri(@"Images/pin.png", UriKind.Relative)), Stretch = Stretch.Fill, Width = 7, Margin = new Thickness(3, 0, 0, 0) });

        for (int i = 0; i < (pins/2)-2; i++)
        {
            UpperPins.Children.Add(new Image() { Source = new BitmapImage(new Uri(@"Images/pin.png", UriKind.Relative)), Stretch = Stretch.Fill, Width = 7, Margin = new Thickness(6, 0, 0, 0) });
            LowerPins.Children.Add(new Image() { Source = new BitmapImage(new Uri(@"Images/pin.png", UriKind.Relative)), Stretch = Stretch.Fill, Width = 7, Margin = new Thickness(6, 0, 0, 0) });

        }
        UpperPins.Children.Add(new Image() { Source = new BitmapImage(new Uri(@"Images/pin.png", UriKind.Relative)), Stretch = Stretch.Fill, Width = 7, Margin = new Thickness(6, 0, 3, 0) });
        LowerPins.Children.Add(new Image() { Source = new BitmapImage(new Uri(@"Images/pin.png", UriKind.Relative)), Stretch = Stretch.Fill, Width = 7, Margin = new Thickness(6, 0, 3, 0) });


    }

    public IC():this(6)
    {

    }
}

public class OddNumberOfPins : Exception
{
    public OddNumberOfPins()
    {
       //TODO
    }
}
}

I'm using Expression Blend to do the XAML


Solution

  • Create a grid, with columns and rows equal to the number of pin holes. Place empty columns/rows to account for empty columns/rows in the real device.

    In each grid cell add a grid. In the inner grids create a 3x3 row/column layout. For the first and last row/column, create * Gridlengths so you can adjust relative sizes between the buffer area and the middle cell.

    In the middle cell of the inner grids, add a rectangle with an ellipse using a drawing brush (so again, it stays relative).

    Here's XAML with the first row filled in.

    <Window x:Class="WpfApplication5.MainWindow"
            xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
            xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
            Title="MainWindow" Height="579" Width="274">
        <Window.Resources>
            <Style TargetType="{x:Type UserControl}" x:Key="PinHole">
                <Setter Property="Template">
                    <Setter.Value>
                        <ControlTemplate>
                            <Grid>
                                <Grid.ColumnDefinitions>
                                    <ColumnDefinition Width="1*"/>
                                    <ColumnDefinition Width="2*"/>
                                    <ColumnDefinition Width="1*"/>
                                </Grid.ColumnDefinitions>
                                <Grid.RowDefinitions>
                                    <RowDefinition Height="1*"/>
                                    <RowDefinition Height="2*"/>
                                    <RowDefinition Height="1*"/>
                                </Grid.RowDefinitions>
    
                                <Rectangle Grid.Row="1" Grid.Column="1">
                                    <Rectangle.Fill>
                                        <DrawingBrush>
                                            <DrawingBrush.Drawing>
                                                <GeometryDrawing Brush="Black">
                                                    <GeometryDrawing.Geometry>
                                                        <GeometryGroup>
                                                            <EllipseGeometry Center="1,1" RadiusX="1" RadiusY="1"/>
                                                            <EllipseGeometry Center="1,1" RadiusX=".5" RadiusY=".5"/>
                                                        </GeometryGroup>
                                                    </GeometryDrawing.Geometry>
                                                </GeometryDrawing>
                                            </DrawingBrush.Drawing>
                                        </DrawingBrush>
                                    </Rectangle.Fill>
                                </Rectangle>
                            </Grid>
                        </ControlTemplate>
                    </Setter.Value>
                </Setter>
            </Style>
        </Window.Resources>
        <Grid>
            <Grid.ColumnDefinitions>
                <ColumnDefinition/>
                <ColumnDefinition/>
                <ColumnDefinition/>
                <ColumnDefinition/>
                <ColumnDefinition/>
                <ColumnDefinition/>
                <ColumnDefinition/>
                <ColumnDefinition/>
                <ColumnDefinition/>
                <ColumnDefinition/>
                <ColumnDefinition/>
                <ColumnDefinition/>
                <ColumnDefinition/>
                <ColumnDefinition/>
                <ColumnDefinition/>
                <ColumnDefinition/>
                <ColumnDefinition/>
            </Grid.ColumnDefinitions>
            <Grid.RowDefinitions>
                <RowDefinition/>
                <RowDefinition/>
                <RowDefinition/>
                <RowDefinition/>
                <RowDefinition/>
                <RowDefinition/>
                <RowDefinition/>
                <RowDefinition/>
                <RowDefinition/>
                <RowDefinition/>
                <RowDefinition/>
                <RowDefinition/>
                <RowDefinition/>
                <RowDefinition/>
                <RowDefinition/>
                <RowDefinition/>
                <RowDefinition/>
                <RowDefinition/>
                <RowDefinition/>
                <RowDefinition/>
                <RowDefinition/>
                <RowDefinition/>
                <RowDefinition/>
                <RowDefinition/>
                <RowDefinition/>
                <RowDefinition/>
                <RowDefinition/>
                <RowDefinition/>
                <RowDefinition/>
                <RowDefinition/>
                <RowDefinition/>
                <RowDefinition/>
                <RowDefinition/>
                <RowDefinition/>
                <RowDefinition/>
                <RowDefinition/>
                <RowDefinition/>
            </Grid.RowDefinitions>
            <UserControl Grid.Column="0" Style="{StaticResource ResourceKey=PinHole}"/>
            <UserControl Grid.Column="1" Style="{StaticResource ResourceKey=PinHole}"/>
            <UserControl Grid.Column="3" Style="{StaticResource ResourceKey=PinHole}"/>
            <UserControl Grid.Column="4" Style="{StaticResource ResourceKey=PinHole}"/>
            <UserControl Grid.Column="5" Style="{StaticResource ResourceKey=PinHole}"/>
            <UserControl Grid.Column="6" Style="{StaticResource ResourceKey=PinHole}"/>
            <UserControl Grid.Column="7" Style="{StaticResource ResourceKey=PinHole}"/>
            <UserControl Grid.Column="9" Style="{StaticResource ResourceKey=PinHole}"/>
            <UserControl Grid.Column="10" Style="{StaticResource ResourceKey=PinHole}"/>
            <UserControl Grid.Column="11" Style="{StaticResource ResourceKey=PinHole}"/>
            <UserControl Grid.Column="12" Style="{StaticResource ResourceKey=PinHole}"/>
            <UserControl Grid.Column="13" Style="{StaticResource ResourceKey=PinHole}"/>
            <UserControl Grid.Column="15" Style="{StaticResource ResourceKey=PinHole}"/>
            <UserControl Grid.Column="16" Style="{StaticResource ResourceKey=PinHole}"/>
        </Grid>
    </Window>
    

    Now, when you add ICs, split them up into visual parts (top-left corner, etc), and arrange the parts in the right grid cell locations, together as a unit.

    In code, you can have an IC unit that you give a top-left cell to, and then the parts of the IC will set their Grid.Row and Grid.Column to the right locations.