0

How to create a general user control using MVVM Light?

All the main views in the application seem to work fine. However, general controls doesn't seem to accept bindings. This is my FileDiplay control. An icon and a TextBlock displaying a filename next to it.


Utilization

In one of the main views, I try to bind a FileName inside an ItemsTemplate of an ItemsControl. Specifying a literal, like FileName="xxx" works fine, but binding doesn't.

<local:FileLink FileName="{Binding FileName}" />

I've been playing around with DependencyProperty and INotifyPropertyChanged a lot. And seemingly there's no way around a DependencyProperty, since it can't be bound otherwise. When using a simple TextBlock instead of this user control, binding is accepted.


I didn't include the locator or the utilizing control in order to avoid too much code. In fact, I think this is a very simple problem that I haven't found the solution for, yet. I do think that having the DataContext set to the ViewModel is correct, since no list binding or real UserControl separation is possible. I've also debugged into the setters and tried the different approaches.

FileLink.xaml

<local:UserControlBase
    x:Class="....FileLink"
    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:..."
    mc:Ignorable="d" DataContext="{Binding FileLink, Source={StaticResource Locator}}">
    <Grid>
        <StackPanel Orientation="Horizontal">
            <Image Source="{Binding Icon}" Margin="0,0,5,0" />
            <TextBlock Text="{Binding FileName}" />
        </StackPanel>
    </Grid>
</local:UserControlBase>

FileLink.xaml.cs

using System.Windows;
using System.Windows.Media;

namespace ...
{
    public partial class FileLink : UserControlBase
    {
        private FileLinkViewModel ViewModel => DataContext as FileLinkViewModel;
        public static DependencyProperty FileNameProperty = DependencyProperty.Register(nameof(FileName), typeof(string), typeof(FileLink));

        public ImageSource Icon
        {
            get
            {
                return App.GetResource("IconFileTypeCsv.png"); // TODO:...
            }
        }
        public string FileName
        {
            get
            {
                return ViewModel.FileName;
            }
            set
            {
                ViewModel.FileName = value;
            }
        }

        public FileLink()
        {
            InitializeComponent();
        }
    }
}

FileLinkViewModel.cs

using GalaSoft.MvvmLight;

namespace ...
{
    public class FileLinkViewModel : ViewModelBase
    {
        private string _FileName;
        public string FileName
        {
            get
            {
                return _FileName;
            }
            set
            {
                Set(() => FileName, ref _FileName, value);
            }
        }
    }
}
bytecode77
  • 14,163
  • 30
  • 110
  • 141
  • "I do think that having the DataContext set to the ViewModel is correct". I don't think so. Explicitly setting a UserControl's DataContext is virtually always wrong. It prevents that the UserControl inherits the DataContext of its parent control, which is what you usually expect when you use the control, e.g. whey you write `FileName="{Binding FileName}"`. – Clemens Dec 11 '16 at 19:17
  • But, wouldn't this mean that property names have to match? I might very well be mistaken about this. When specifying no DataContext, how do I set up the properties correctly? – bytecode77 Dec 11 '16 at 19:18
  • In the UserControl's XAML, you'll write the Bindings with RelativeSource. See here: http://stackoverflow.com/a/25699347/1136211 – Clemens Dec 11 '16 at 19:19
  • You mean the FileDisplay, or the parent control? And also, is it *always* wrong to specify a DataContext? The main views are tab pages in the main window and completely on their own. Would you consider it good practice to set their DataContext and avoid this for generalized views? – bytecode77 Dec 11 '16 at 19:21
  • I mean the internal bindings, e.g. ``. IMO, explicitly setting a UserControl's DataContext is always wrong. – Clemens Dec 11 '16 at 19:23
  • Interesting! It works immediately. Is there a way to simplify it by putting a reference into the local UserControl.Resources? Also, I don't use GetValue and SetValue, but the ViewModel instead. What would you prefer? – bytecode77 Dec 11 '16 at 19:26
  • Your FileName property doesn't seem to make any sense at all. Why wrap a view model property in the control when `Text="{Binding FileName}"` would address the view model property directly anyway? – Clemens Dec 11 '16 at 19:26
  • Isn't this because the properties match? What if the parent control's property was "SourceCsvFileName", then it would not work and the separation would be defeated? – bytecode77 Dec 11 '16 at 19:27
  • 1
    Then your UserControl would have a dependency property that would be bound to the view model, and the "internal" binding (in the control's XAML) would bind to that dependency property with RelativeSource. – Clemens Dec 11 '16 at 19:30
  • You created a DataContext for your UserControl, which completely breaks databinding. Does a TextBox have a TextBoxViewModel? No. A UserControl should be designed just like every other UI element. Remember that and you'll never get bitten by this again. –  Dec 12 '16 at 15:33
  • @Will This makes me consider MVVM at all... MVVM Light makes it easy to do just `{Binding PropertyName` and bam, you're done. So from what I've read here, this only works because one expects a "view", like a part of the whole application rather than a UserControl? – bytecode77 Dec 12 '16 at 15:37
  • No, you just don't create view models for your user controls. A user control is a control, and should be designed like one. Please think--***does the TextBox have a TextBoxViewModel?*** No, it has public properties for what it needs. Its DataContext is the data context of the code that is using the control. –  Dec 12 '16 at 17:45
  • So, despite this question being answered: When *does* MVVM ViewModels come into play then? Is it for controls that don't have an otherwise external DataContext? – bytecode77 Dec 12 '16 at 17:47

1 Answers1

2

Do not explicitly set the DataContext of your UserControl, because it effectively prevents that the control inherits the DataContext from its parent control, which is what you expect in a Binding like

<local:FileLink FileName="{Binding FileName}" />

Also, do not wrap the view model properties like you did with the FileName property. If the view model has a FileName property, the above binding works out of the box, without any wrapping of the view model.

If you really need a FileName property in the UserControl, it should be a regular dependency property

public partial class FileLink : UserControlBase
{
    public FileLink()
    {
        InitializeComponent();
    }

    public static readonly DependencyProperty FileNameProperty =
        DependencyProperty.Register(nameof(FileName), typeof(string), typeof(FileLink));

    public string FileName
    {
        get { return (string)GetValue(FileNameProperty); }
        set { SetValue(FileNameProperty, value); }
    }
}

and you should bind to it by specifying the UserControl as RelativeSource:

<local:UserControlBase ...> <!-- no DataContext assignment -->
    <StackPanel Orientation="Horizontal">
        <Image Source="IconFileTypeCsv.png" Margin="0,0,5,0" />  
        <TextBlock Text="{Binding FileName,
                          RelativeSource={RelativeSource AncestorType=UserControl}}" />
    </StackPanel>
</local:UserControlBase>
Clemens
  • 123,504
  • 12
  • 155
  • 268
  • So I also don't need the ViewModel anymore and best of all: It works! :) +1 ... Side note: Is there any way to simplify this binding expression? If not, your answer is far sufficient and brings me closer to understanding WPF more. – bytecode77 Dec 11 '16 at 19:53
  • You mean simplify the RelativeSource part? You could assign `x:Name` on the UserControl and use ElementName instead of RelativeSource. – Clemens Dec 11 '16 at 19:57