2

I haven't been doing much in WPF recently, so decided to do something I could use, so first I'm trying to make something like interactive form, to reduce usage of paper. So here it's what I'm doing, I have a scan of the original form, it's saved in .png format which I added as resource, then set it as window's background image via:

<Window.Background>
    <ImageBrush ImageSource="pictures/podstawowa_front.png" />
</Window.Background>

That works perfectly fine, but now in the place where a user is meant to enter some data, I wanted to put WPF controls, which is also fine. The problem comes when I want to resize the window, the background image resize differently than the control. I have tried using different containers for the controls but none seems to work. Also removed all height, width, alignments from the control, and left just margin (so it has some starting position).

Example:

At startup - it's more or less at correct position with similar size:

Application startup

After resizing window:

After resizing window

So you can see that I shrunk the window a bit, but the control was shrunken more and was not moved at all.

I have tried googling and looking for similar problems but found hardly anything, tried to messing up with the different containers, transformations but nothing seems to work as I'd like it to. (I'm also trying to stick just to XAML)

I probably could solve it with using lots of nested containers but I'm trying to avoid it as it seems to be an overkill and I wonder if there is some other faster and more elegant way of doing that. Feel free to ask for additional details in comments. Thanks in advance for any tips.

XAML code:

<Window x:Class="DnD.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="MainWindow" Height="768" Width="1366">
    <Window.Background>
        <ImageBrush ImageSource="pictures/basic_front.png" />
    </Window.Background>

    <Grid>
        <TextBox Margin="265,307,970,413" TextWrapping="Wrap" Text="TextBox" FontSize="10"/>
    </Grid>

</Window>
Fus Ro Dah
  • 331
  • 5
  • 22
Tafari
  • 2,639
  • 4
  • 20
  • 28
  • Just a shot in the dark but does this link help? http://stackoverflow.com/questions/4495947/how-to-make-textbox-resize-as-the-window-resize – thinklarge Jan 05 '16 at 16:51
  • Not really, it's just default resizing thanks to using grid. In my case I need both to resize and move the controls accordingly, same as it is done with image in the background. – Tafari Jan 05 '16 at 17:02
  • I had to do A LOT of this exact thing literally making an exact copy of paper forms. I found working around an image background becomes a pain in the *** for multiple reasons. It's better (and often faster) to just do your layout correctly and mimic the form visually but not literally use an image of it. End result were screens that looked exactly like the forms visually, but they would do any size no problem + no image files taking up space. Also made updates/changes to the forms much easier. Just my two cents. In time they ended up just printing the digital stuff to use as paper forms also. – Chris W. Jan 05 '16 at 17:42
  • @Chris W. Thanks for sharing, yes that's what I've found out so far also unfortunately... but since it's nothing necessary and it's just for myself, thought about asking here in case someone knows some solution : ) But for now that's probably the solution I'm gonna take, just to copy form's layout just with controls without any image backgrounds. – Tafari Jan 05 '16 at 17:51
  • I mean if you really wanted to, you could embed the whole thing in a ViewBox as quicky workaround. However that's not exactly optimal. – Chris W. Jan 05 '16 at 19:15
  • @Chris W. ViewBox is quite nice, but yeah it's not really that good, I can see that in my case when i resize the window to the right (making it wider) it doesn't really make the textbox wider, however when i expand it to bottom, or shrink to left it, resize the element quite nice but moves it too much. Thanks for the tip anyway ; ) – Tafari Jan 07 '16 at 09:19

2 Answers2

3

This happens because the Margin remains the same when the Window size is changed. So when you reduce the width of the Window, the TextBox remains the same distance from the left and right edges.

If you have to support resizing, you really can't do this with just XAML. If you can go beyond XAML, you could bind the Margins to the Height and Width of the Window and write a converter to change the Margins to correctly position the TextBox.

If you don't actually need to support resizing, just set the ResizeMode for the window to NoResize.

A better approach to positioning controls over an image is to use a Canvas instead of a Grid, and place the image inside the Canvas instead of as the window background. Then the TextBox can be placed in the appropriate place over the image using the Canvas properties instead of using the Margin. This won't solve the resizing problem, although this way you can leave things at their original size when the window is resized.

JNP
  • 131
  • 2
  • 5
  • The margins bindings seem interesting, I'll give it a shot and see, thanks! : ) – Tafari Jan 05 '16 at 22:07
  • Okay, so I tried this and it probably could work, but the binding would need some calculations ( starting position's margin multiplied by window's height/width% change, or something similar). Since different controls are on different positions I would need a converter for every single control, since you can pass the values only by converter's constructor right? – Tafari Jan 07 '16 at 09:27
  • You can set a single parameter in the ConverterParameter in your Binding. This is passed in as the "parameter" in your Converter's Convert method. You could use this to write just one converter to use everywhere which multiplies by the factor, and set the factors in the XAML. – JNP Jan 07 '16 at 17:27
  • Posted my solution it works kinda ok but it's not perfect, tip with margin bindings was helpful +1 and accepted answer since you pointed me in the right direction : ) – Tafari Jan 08 '16 at 21:33
0

Well It's not 100% what I've wanted to achieve but this closes thing I've managed to do (thanks to JNP for tip about margin binding). So basically what I'm doing is to use multiconverter for margin bindings, while passing the starting position of the control as ConverterParameter, margins are depended on window's height and width ratio change (this is the only flow, as it seems the background image is transformed slightely different).

The final result moves and resizes the control quite nice but the more we resize the window the difference starts to be more noticable, it's probably due to mentioned transformation difference.

Code:

ImageLayout.xaml:

    Title="Character sheet - Front" Height="768" Width="1366" SizeChanged="Window_SizeChanged">
<Window.Resources>
    <local:MarginConverter x:Key="MyConverter"/>
</Window.Resources>
<Window.Background>
    <ImageBrush ImageSource="Resources/base_front.png"/>
</Window.Background>
<Grid>
    <TextBox x:Name="TextBox" TextWrapping="Wrap" Text="TextBox">
        <TextBox.Margin>
            <MultiBinding Converter="{StaticResource MyConverter}" ConverterParameter="266,307,962,408">
                <Binding RelativeSource="{RelativeSource AncestorType={x:Type Window}}" Path="WindowProperties.CurrentWidthChange" />
                <Binding RelativeSource="{RelativeSource AncestorType={x:Type Window}}" Path="WindowProperties.CurrentHeightChange"/>
            </MultiBinding>
        </TextBox.Margin>
    </TextBox>
</Grid>

As said converter takes the base margin in ConverterParameter and is bound to width/height ratio change, (which is kept in additional class, since Window is not DependencyObject).

ImageLayout.xaml.cs:

public partial class ImageLayout : Window
{
    public WindowProperties WindowProperties { get; set; }

    public ImageLayout()
    {
        WindowProperties = new WindowProperties();

        InitializeComponent();
    }

    private void Window_SizeChanged(object sender, SizeChangedEventArgs e)
    {
        WindowProperties.CurrentHeightChange = e.NewSize.Height / 768;
        WindowProperties.CurrentWidthChange = e.NewSize.Width / 1366;
    }
}

Here we just have the instance of WindowProperties, which is class implementing INotifyPropertyChanged, additionally I've overriden an event which is called on window's size change, in order to update the ratio values of both width and height change.

WindowProperties.cs:

public class WindowProperties : INotifyPropertyChanged
{
    public WindowProperties()
    {
        CurrentHeightChange = 1;
        CurrentWidthChange = 1;
    }

    private double _currentHeightChange;
    public double CurrentHeightChange
    {
        get
        {
            return _currentHeightChange;
        }
        set
        {
            _currentHeightChange = value;
            NotifyPropertyChanged("CurrentHeightChange");
        }
    }

    private double _currentWidthChange;
    public double CurrentWidthChange
    {
        get
        {
            return _currentWidthChange;
        }
        set
        {
            _currentWidthChange = value;
            NotifyPropertyChanged("CurrentWidthChange");
        }
    }

    public event PropertyChangedEventHandler PropertyChanged;

    protected virtual void NotifyPropertyChanged(string property)
    {
        if (PropertyChanged != null)
        {
            PropertyChanged(this, new PropertyChangedEventArgs(property));
        }
    }
}

Additional class implementing INotifyPropertyChanged, needed for converter's bindings purpose.

MarginConverter.cs:

public class MarginConverter : IMultiValueConverter
{

    public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
    {
        string[] positions = parameter.ToString().Split(',');
        double leftPos = double.Parse(positions[0]);
        double topPos = double.Parse(positions[1]);
        double rightPos = double.Parse(positions[2]);
        double bottomPos = double.Parse(positions[3]);

        var actualMargin = new Thickness(leftPos, topPos, rightPos, bottomPos);

        return new Thickness(actualMargin.Left * (double)values[0],
                             actualMargin.Top * (double)values[1],
                             actualMargin.Right * (double)values[0],
                             actualMargin.Bottom * (double)values[1]);
    }

    public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
    {
        return null;
    }
}

Converter basically takes the starting position from parameter and then multiplies it by ratio of window's size change.

Results:

Application start:

Application start

Resize:

After resize

A lot of resizing, causing bad transformations:

Resizing flaw

Conclusion:

As you can see it works pretty good and could be use if the resize options would be limited, to make it better there should be some improvement done to ratio computation, but unfortunately I do not know what exact transformations are done to the background image when resizing the window. The improvement also seems to be bit an overkill and in general in my opinion it's better to copy the image layout just with controls without having image in the background.

Tafari
  • 2,639
  • 4
  • 20
  • 28