1

I am trying to do a small thing with DataBinding in WPF and C#, but I can't understand any of the tutorials for DataBinding.. my problem is, that I have a character class in C# and it has X and Y Coordinates with get and set functions. Then I have an image in a canvas in the window and now I am trying to bind the coordinates from the character class to the image. (There is only one image, and there will be created only one instance of the character class, though not at the beginning). Hope anyone can explain it so that I can fully understand it :/

EDIT:

Here is the XAML:

<Window x:Class="ProjectChar.MainWindow"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    Title="MainWindow" Height="800" Width="1000" Background="Gray">
<Viewbox StretchDirection="Both" Stretch="Uniform">
    <Grid>
        <Canvas Name="Canv" Background="White" Visibility="Hidden" MaxHeight="750" MaxWidth="1000">
            <Canvas.LayoutTransform>
                <ScaleTransform ScaleY="-1"/>
            </Canvas.LayoutTransform>
            <Image Name="CharImage"  Source="CharBild.png" Canvas.Left="{Binding iXCoordinate}" Canvas.Top="{Binding iYCoordinate}"/>
        </Canvas>
    </Grid>
</Viewbox>

and here is the C#-Part:

namespace ProjectChar
{
    public class Char : MainWindow
    {
        public int iXCoordinate { get; set; }
        public int iYCoordinate { get; set; }
    }
}

So I somehow need to bind them together, but I don't exactly know how. I know that I have to create a DataContext and that I need to set the UpdateSource to PropertyChanged and that I need an EventHandler for the Property Changed, but I don't know how to do any of that and the tutorials on the internet are all saying kind of different things :/

khellang
  • 17,550
  • 6
  • 64
  • 84
dingoglotz
  • 2,553
  • 3
  • 17
  • 21
  • Show us the xaml code and the class which should bind to the image. – ΩmegaMan Nov 20 '12 at 19:10
  • Well I tried a lot of tutorials (for example, I tried first setting the resource in the window, but that failed, because I could not create a dataContext or something like that :s all these tutorials say other things and I don't really get what I need to do this, but I think it is rather simple :/) – dingoglotz Nov 20 '12 at 19:13
  • I added the code that I have :/ – dingoglotz Nov 20 '12 at 19:29
  • Does the image show but the coordinates are wrong, or does the image not show at all? – Josh C. Nov 20 '12 at 19:57
  • Why do you have the -1 `ScaleTransform`? – khellang Nov 20 '12 at 19:58
  • 1
    I like to blog about beginner WPF concepts, and if you're struggling to understand data binding than you might be interested in checking out my post [What is this "DataContext" you speak of?](http://rachel53461.wordpress.com/2012/07/14/what-is-this-datacontext-you-speak-of/). The important part to understand is that bindings are used to access data in your `DataContext` that sits behind the UI objects, so for your binding to work you need to be sure your UI object has a `DataContext` containing those properties behind it. – Rachel Nov 20 '12 at 20:11
  • Thanks Rachel, I will read it later, but my problem is in implementing the code, what I'm doing is clear to me :) – dingoglotz Nov 20 '12 at 20:25

1 Answers1

3

To bind the Image's X and Y coordinates to your properties, do this:

  1. Remove Char's inheritance from MainWindow.
  2. Set MainWindow's DataContext to an instance of Char.
  3. Remove Visibility="Hidden" from the Canvas

Example:

public class MainWindow : Window
{
    public MainWindow()
    {
        InitializeComponent();
        DataContext = new Char { iXCoordinate = 20, iYCoordinate = 50 };
    }
}

Make sure you actually set the iXCoordinate and iYCoordinate properties to something. If not, they will be the default value of int, which is 0. E.g. the image will show in the upper left corner.

To let the View (UI) know when a bound value have changed, implement INotifyPropertyChanged and raise the PropertyChanged event every time a property is set:

public class Char : INotifyPropertyChanged
{
    private int _iXCoordinate;

    public int iXCoordinate
    {
        get { return _iXCoordinate; }
        set
        {
            _iXCoordinate = value;
            OnPropertyChanged("iXCoordinate");
        }
    }

    private int _iYCoordinate;

    public int iYCoordinate
    {
        get { return _iYCoordinate; }
        set
        {
            _iYCoordinate = value;
            OnPropertyChanged("iYCoordinate");
        }
    }

    public event PropertyChangedEventHandler PropertyChanged;

    private void OnPropertyChanged(string propertyName)
    {
        var handler = PropertyChanged;
        if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
    }
}

This is the most simple way to implement INotifyPropertyChanged. However, I would recommend to make a base class which implements the interface and let your ViewModels inherit from that. Try something like this:

public class PropertyChangedBase : INotifyPropertyChanged
{
    public event PropertyChangedEventHandler PropertyChanged;

    protected void OnPropertyChanged<T>(Expression<Func<T>> propertyExpression)
    {
        var handler = PropertyChanged;
        var propertyName = GetPropertyName(propertyExpression);
        if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
    }

    private static string GetPropertyName<T>(Expression<Func<T>> propertyExpression)
    {
        var memberExpression = propertyExpression.Body as MemberExpression;
        if (memberExpression == null)
        {
            var unaryExpression = propertyExpression.Body as UnaryExpression;
            if (unaryExpression == null) 
                throw new ArgumentException("Expression must be a MemberExpression.", "propertyExpression");

            memberExpression = unaryExpression.Operand as MemberExpression;
        }

        if (memberExpression == null) 
            throw new ArgumentException("Expression must be a MemberExpression.", "propertyExpression");

        var propertyInfo = memberExpression.Member as PropertyInfo;
        if (propertyInfo == null) 
            throw new ArgumentException("Expression must be a Property.", "propertyExpression");

        return propertyInfo.Name;
    }
}

This PropertyChangedBase also has the added benefit that you have to raise the PropertyChanged event by using a lambda expression instead of using "magic strings", which is prone to typos and bad for refactoring:

    public int iYCoordinate
    {
        get { return _iYCoordinate; }
        set
        {
            _iYCoordinate = value;
            OnPropertyChanged(() => iYCoordinate);
        }
    }

However, your REAL problem is the ViewBox which wraps the Grid.

A ViewBox measures it's children to a size of Infinity, it then arranges them by their DesiredSize. This means that it's children can be whatever size they want to be. Both Grid and Canvas have no size they want to be, they rely on their parent to give them a size.

To solve your problem, specify Width and Height of either the Grid or the Canvas inside it.

BTW: You know that

<Canvas.LayoutTransform>
  <ScaleTransform ScaleY="-1"/>
</Canvas.LayoutTransform>

will flip your cavas, right?

khellang
  • 17,550
  • 6
  • 64
  • 84
  • Quick Question, because I can't try the solution at the moment: If I understand it correct, you create a new instance of the Char-Class and set it as the DataContext, right? Can I, or rather how can I, access methods of this instance? For example, the Character has a method called walk, how can I call this method with the instance? Hope it is understandable :/ – dingoglotz Nov 20 '12 at 20:22
  • Well, if you look at my example, you already have the instance of `Char` when you set the `DataContext`. You can do whatever you want with it. If you want to get it later, you can do `Char character = DataContext as Char;` – khellang Nov 20 '12 at 20:31
  • @dingoglotz You'd have to create an instance of `Char` then set the DataContext instead of making it dynamic. `Char charInstance = new Char() { iXCoordinate = 20, iYCoordinate = 50 }; DataContext = charInstance;` or dynamically retrieve it as needed as khellang suggests above. – Bob. Nov 20 '12 at 20:32
  • 1
    Also, if you're looking to do WPF work, I really recommend using the MVVM pattern for decoupling your application. Check out [A Step By Step Guide to MVVM](http://www.codeproject.com/Articles/221585/Step-By-Step-Guide-To-MVVM) or [A Simple Tutorial for Absolute Beginners](http://www.codeproject.com/Articles/126249/MVVM-Pattern-in-WPF-A-Simple-Tutorial-for-Absolute).. – khellang Nov 20 '12 at 20:36
  • @Bob Quit trying to edit my post to add parentheses behind `Char`! They **DO NOT** need to be there, it is perfectly valid, compiling code without them... – khellang Nov 20 '12 at 20:40
  • @Bob Read more about it in [Why are C# 3.0 object initializer constructor parentheses optional?](http://stackoverflow.com/questions/3661025/why-are-c-sharp-3-0-object-initializer-constructor-parentheses-optional) ;) – khellang Nov 20 '12 at 20:53
  • Ah. Parameterless constructors. I don't use those much. lol Reminds me of C constructor code though. – Bob. Nov 20 '12 at 20:57
  • Okay it works perfectly, but I need to update the binding, how can I do that? (Manually or automatically, it does not matter :D) – dingoglotz Nov 20 '12 at 21:36