0

I'm building a Point of Sale system using WPF and I've ran into a brick wall.

So one of the components I'm trying to implement is the "Item Panel" (Canvas) which hosts "Items Buttons" (Button). This panel is used to add buttons which represent items that are used in the system. You can lock/unlock the panel to allow you to move newly created buttons to the location the user pleases.

I want to have a dependency property on Item Buttons that indicate if the buttons are locked (or not). Creating/Locking/unlocking Item Buttons is done by using a context menu in Item Panel.

Below I've included my code:

ItemPanel.xaml

<Window x:Class="ItemPanel.Views.ItemPanelView"
    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:ItemPanel"
    xmlns:viewModels="clr-namespace:ItemPanel.ViewModels"
    xmlns:views="clr-namespace:ItemPanel.Views"
    mc:Ignorable="d"
    d:DataContext="{d:DesignInstance Type=viewModels:ItemPanelViewModel, IsDesignTimeCreatable=True}"
    Title="ItemPanelView" 
    Height="800" Width="800">

<ItemsControl ItemsSource="{Binding ItemButtons}">

    <!--ItemPanel-->
    <ItemsControl.ItemsPanel>
        <ItemsPanelTemplate>
            <Canvas x:Name="ItemPanel" 
                    Background="LightGray"
                    ClipToBounds="True"
                    Focusable="False"
                    Width="400"
                    Height="400">
                <Canvas.ContextMenu>
                    <ContextMenu>
                        <MenuItem Header="Add Item"
                                  Command="{Binding CreateItemButtonCommand}"/>
                        <MenuItem Header="Lock Panel"
                                  IsCheckable="True"
                                  IsChecked="{Binding IsLocked}"
                                  Command="{Binding LockItemPanelCommand}"
                                  CommandParameter="{Binding RelativeSource={RelativeSource Self}, Path=IsChecked}"/>
                    </ContextMenu>
                </Canvas.ContextMenu>
            </Canvas>
        </ItemsPanelTemplate>
    </ItemsControl.ItemsPanel>

    <!--ItemButton-->
    <ItemsControl.ItemTemplate>
        <DataTemplate>
            <views:ItemButtonView x:Name="ItemButton" Lock="{Binding DataContext.IsLocked, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Window}}}"/>
        </DataTemplate>
    </ItemsControl.ItemTemplate>

</ItemsControl>

ItemPanelView.xaml.cs

namespace ItemPanel.Views
{
    /// <summary>
    /// Interaction logic for ItemPanelView.xaml
    /// </summary>
    public partial class ItemPanelView : Window
    {
        public ItemPanelView()
        {
            InitializeComponent();
            DataContext = new ItemPanelViewModel();
        }
    }
}

ItemPanelViewModel.cs

namespace ItemPanel.ViewModels
{
    public class ItemPanelViewModel : ViewModelBase
    {

        private bool _isLocked;

        public ItemPanelViewModel()
        {
            IsLocked = true;
            ItemButtons = new ObservableCollection<ItemButtonViewModel>();

            CreateItemButtonCommand = new DelegateCommand(CreateItemButtonCommandHandler);
            LockItemPanelCommand = new DelegateCommand<bool?>(LockItemPanelCommandHandler);
        }

        public ObservableCollection<ItemButtonViewModel> ItemButtons { get; private set; }

        #region Create ItemButton

        public DelegateCommand CreateItemButtonCommand { get; private set; }

        private void CreateItemButtonCommandHandler()
        {
            //ItemButtonViewModel viewModel = _container.Resolve<ItemButtonViewModel>();
            ItemButtonViewModel viewModel = new ItemButtonViewModel() { Label = Guid.NewGuid()};
            ItemButtons.Add(viewModel);
        }

        #endregion

        #region Lock ItemPanel

        public bool IsLocked
        {
            get
            {
                return _isLocked;                
            }
            set
            {
                _isLocked = value;
                OnPropertyChanged("IsLocked");
            }
        }

        public DelegateCommand<bool?> LockItemPanelCommand { get; private set; }

        public void LockItemPanelCommandHandler(bool? isChecked)
        {
            //if (!isChecked.HasValue)
            //    return;

            //foreach (ItemButtonViewModel itemButton in ItemButtons)
            //{
                //itemButton.IsLocked = IsLocked;
            //}

        }

        #endregion
    }
}

ItemButtonView.xaml

<Button x:Class="ItemPanel.Views.ItemButtonView"
             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:ItemPanel.Views"
             xmlns:viewModels="clr-namespace:ItemPanel.ViewModels"
             mc:Ignorable="d"
             d:DataContext="{d:DesignInstance Type=viewModels:ItemButtonViewModel, IsDesignTimeCreatable=True}"
             Width="100" Height="100"
             Content="{Binding Label}"
             ToolTip="{Binding Label}">

</Button>

ItemButtonView.xaml.cs

namespace ItemPanel.Views
{
    /// <summary>
    /// Interaction logic for ItemButtonView.xaml
    /// </summary>
    public partial class ItemButtonView : Button
    {

        public ItemButtonView()
        {
            InitializeComponent();
        }

        public static readonly DependencyProperty LockProperty =
                DependencyProperty.Register("Lock", typeof(bool), typeof(ItemButtonView));

        public bool Lock
        {
            get { return (bool)GetValue(LockProperty); }
            set { SetValue(LockProperty, value); }
        }
    }
}

ItemButtonViewModel.cs

namespace ItemPanel.ViewModels
{
    public class ItemButtonViewModel : ViewModelBase
    {    
        public Guid Label { get; set; }    
    }
}

So my question is, why is the Lock dependency property in the ItemButton view, not being updated by the ItemPanel IsLocked Property.

From my understanding is that it should be bound... but nothing seems to be getting updated.

I would really appreciate any help given.

Thanks

Firas
  • 96
  • 8
  • Dependency property metadata ? – aybe Aug 28 '16 at 03:05
  • Sorry, but I don't understand your comment? What do you mean dependancy property metadata? – Firas Aug 28 '16 at 03:07
  • http://stackoverflow.com/questions/4182165/dependency-property-is-not-updating-my-usercontrol – aybe Aug 28 '16 at 03:07
  • In the example you gave, the person binds the dependency property CellValue to CellNo in the the codebehind and not the viewmodel... am I missing something? – Firas Aug 28 '16 at 03:20
  • Look at the metadata defined in the dependency property. – aybe Aug 28 '16 at 03:34
  • I really appreciate the help @Aybe, but it's not working. I added BindsTwoWayByDefault = true and the value doesn't get updated. Is it cause its in a ItemsControl? – Firas Aug 28 '16 at 03:58
  • What MVVM framework are you using ? – aybe Aug 28 '16 at 03:59
  • Sorry i'm a complete noobie. I'm just trying to adhere to MVVM concepts. I don't really have a framework (that I know off) – Firas Aug 28 '16 at 04:02
  • Take a look at the little example I crafted for you :) Btw IMO there's no point in individually locking controls, just the canvas (what I did). – aybe Aug 28 '16 at 05:05

1 Answers1

0

I used MVVM Light for this example.

So you have

  • a Canvas with a context menu to: add something, lock movement
  • content can be moved within the Canvas if unlocked
  • a simple model, content can be anything, interaction will be handled
  • a template selector to assign your content an UI
  • etc ...

enter image description here

XAML

<Window x:Class="WpfApplication6.MainWindow"
        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:WpfApplication6"
        xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
        mc:Ignorable="d" d:DataContext="{d:DesignInstance local:MyModel}"
        Title="MainWindow" Height="350" Width="525" x:Name="Window">
    <Window.Resources>
        <local:MyTemplateSelector x:Key="MyTemplateSelector" />
        <DataTemplate x:Key="SimpleContentTemplate"   DataType="local:SimpleContent">
            <Grid Width="200" Height="200" Background="DeepSkyBlue">
                <TextBlock Text="{Binding Text}"  HorizontalAlignment="Center" VerticalAlignment="Center"/>
            </Grid>
        </DataTemplate>
    </Window.Resources>
    <Grid>
        <ItemsControl ItemsSource="{Binding Elements}" >
            <ItemsControl.ItemsPanel>
                <ItemsPanelTemplate>
                    <Canvas x:Name="Canvas1" IsItemsHost="True" Background="LightBlue">
                        <Canvas.ContextMenu>
                            <ContextMenu>
                                <MenuItem Header="Add button" Command="{Binding AddButton}" IsEnabled="{Binding IsUnlocked}" />
                                <MenuItem Header="Lock" IsChecked="{Binding IsLocked}" Command="{Binding Lock}" />
                            </ContextMenu>
                        </Canvas.ContextMenu>
                    </Canvas>
                </ItemsPanelTemplate>
            </ItemsControl.ItemsPanel>
            <ItemsControl.ItemTemplate>
                <DataTemplate >
                    <ContentControl Content="{Binding}" ContentTemplateSelector="{StaticResource MyTemplateSelector}">
                        <i:Interaction.Behaviors>
                            <local:MovableBehavior Canvas="{Binding ElementName=Canvas1}" CanMove="{Binding ElementName=Window, Path=DataContext.IsUnlocked }"/>
                        </i:Interaction.Behaviors>
                    </ContentControl>
                </DataTemplate>
            </ItemsControl.ItemTemplate>
        </ItemsControl>
    </Grid>
</Window>

Code (C#6)

using System;
using System.Collections.ObjectModel;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Interactivity;
using System.Windows.Media;
using GalaSoft.MvvmLight;
using GalaSoft.MvvmLight.CommandWpf;

namespace WpfApplication6
{
    public partial class MainWindow
    {
        public MainWindow()
        {
            InitializeComponent();
            DataContext = new MyModel();
        }
    }

    public class MyModel : ViewModelBase
    {
        private bool _isLocked;

        public MyModel()
        {
            Lock = new RelayCommand(() =>
            {
                IsLocked = !IsLocked;
                RaisePropertyChanged(() => IsLocked);
            });

            AddButton = new RelayCommand(() => { Elements.Add(new SimpleContent {Text = "Text"}); });

            Elements = new ObservableCollection<object>();
        }

        public ObservableCollection<object> Elements { get; }

        public RelayCommand Lock { get; }

        public RelayCommand AddButton { get; }

        public bool IsUnlocked => !IsLocked;

        public bool IsLocked
        {
            get { return _isLocked; }
            set
            {
                Set(ref _isLocked, value);
                RaisePropertyChanged(() => IsUnlocked);
            }
        }
    }

    public class SimpleContent
    {
        public string Text { get; set; }
    }

    public class MovableBehavior : Behavior<UIElement>
    {
        public static readonly DependencyProperty CanvasProperty = DependencyProperty.Register(
            "Canvas", typeof(Canvas), typeof(MovableBehavior), new PropertyMetadata(default(Canvas)));

        public static readonly DependencyProperty CanMoveProperty = DependencyProperty.Register(
            "CanMove", typeof(bool), typeof(MovableBehavior), new PropertyMetadata(default(bool)));

        private bool _isDragging;

        private Point _point;

        public Canvas Canvas
        {
            get { return (Canvas) GetValue(CanvasProperty); }
            set { SetValue(CanvasProperty, value); }
        }

        public bool CanMove
        {
            get { return (bool) GetValue(CanMoveProperty); }
            set { SetValue(CanMoveProperty, value); }
        }

        protected override void OnAttached()
        {
            AssociatedObject.MouseLeftButtonDown += AssociatedObject_MouseLeftButtonDown;
            AssociatedObject.MouseLeftButtonUp += AssociatedObject_MouseLeftButtonUp;
            AssociatedObject.MouseMove += AssociatedObject_MouseMove;
        }

        private void AssociatedObject_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
        {
            _isDragging = true;
            _point = e.GetPosition(AssociatedObject);
            AssociatedObject.CaptureMouse();
        }

        private void AssociatedObject_MouseLeftButtonUp(object sender, MouseButtonEventArgs e)
        {
            _isDragging = false;
            _point = default(Point);
            AssociatedObject.ReleaseMouseCapture();
        }

        private void AssociatedObject_MouseMove(object sender, MouseEventArgs e)
        {
            if (!_isDragging || !CanMove)
                return;

            var position = e.GetPosition(Canvas) - _point;
            var parent = VisualTreeHelper.GetParent(AssociatedObject) as ContentPresenter;
            if (parent == null) throw new ArgumentNullException(nameof(parent));
            Canvas.SetLeft(parent, position.X);
            Canvas.SetTop(parent, position.Y);
        }
    }

    public class MyTemplateSelector : DataTemplateSelector
    {
        public override DataTemplate SelectTemplate(object item, DependencyObject container)
        {
            var element = container as FrameworkElement;
            if (element != null)
            {
                if (item is SimpleContent)
                {
                    return element.FindResource("SimpleContentTemplate") as DataTemplate;
                }
            }

            return null;
        }
    }
}

You have quite some material to keep you busy, I encourage you to take a look at the documentation of types I have used !

aybe
  • 15,516
  • 9
  • 57
  • 105