I want to create a canvas where a user can drop UI elements (representing tasks). He can then drag them to rearrange them. The elements are contained in an ObservableCollection that is the DataContext.
I can set the Left and Top properties of the Canvas, but the objects position is not affected. Any ideas?
Thanks,
Karel
UPDATE: forgotton ItemsControl descendant (thanks Phil):
public class CustomItemsCollection : ItemsControl
{
protected override void PrepareContainerForItemOverride(DependencyObject element, object item)
{
FrameworkElement contentitem = element as FrameworkElement;
// contentitem.HorizontalAlignment = System.Windows.HorizontalAlignment.Left;
// contentitem.VerticalAlignment = System.Windows.VerticalAlignment.Top;
Binding leftBinding = new Binding("Left");
leftBinding.Mode = BindingMode.TwoWay;
contentitem.SetBinding(Canvas.LeftProperty, leftBinding);
Binding topBinding = new Binding("Top");
topBinding.Mode = BindingMode.TwoWay;
contentitem.SetBinding(Canvas.TopProperty, topBinding);
base.PrepareContainerForItemOverride(element, item);
}
}
More info:
Binding objects derived from usercontrol provoce an exception so after some googling I created a valueconverter:
public class UIElementWrapper : IValueConverter
{
private Dictionary<object, object> CollectionsPool = new Dictionary<object, object>();
#region IValueConverter Members
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
if (value is INotifyCollectionChanged && value is IList)
{
((INotifyCollectionChanged)value).CollectionChanged += UIElementWrapper_CollectionChanged;
var result = new ObservableCollection<ProxyObject>();
foreach (var item in (IList)value)
{
result.Add(new ProxyObject(item));
}
CollectionsPool.Add(result, value);
return result;
}
else
{
throw new ArgumentException("value");
}
}
void UIElementWrapper_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
if (CollectionsPool.ContainsValue(sender))
{
foreach (IList result in CollectionsPool.Keys)
{
if (CollectionsPool[result] == sender)
{
switch (e.Action)
{
case NotifyCollectionChangedAction.Add:
var index = e.NewStartingIndex;
foreach (var item in e.NewItems)
{
result.Insert(index++, new ProxyObject(item));
}
break;
case NotifyCollectionChangedAction.Remove:
foreach (var item in e.OldItems)
{
var deleteList = new List<ProxyObject>();
foreach (ProxyObject p in result)
{
if (p.Value == item) deleteList.Add(p);
}
foreach (var p in deleteList)
{
result.Remove(p);
}
}
break;
case NotifyCollectionChangedAction.Replace:
result[e.OldStartingIndex] = new ProxyObject(e.NewItems[0]);
break;
case NotifyCollectionChangedAction.Reset:
result.Clear();
break;
default:
break;
}
}
}
}
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
#endregion
}
public class ProxyObject
{
public ProxyObject(object value)
{
Value = value;
}
public object Value { get; private set; }
}
This works. But when I bind Left and Top properties of the elements contained in the ObservableCollection they are not used: all items are placed at position 0,0
Here is the code behind:
public MainPage()
{
InitializeComponent();
ObservableCollection<Border> items = new ObservableCollection<Border>();
double left = 0.0;
double top = 0.0;
int i = 0;
Border item = (Border)XamlReader.Load(
"<Border Background=\"Green\" Height=\"140\" Width=\"180\" xmlns=\"http://schemas.microsoft.com/client/2007\"></Border>");
item.Name = string.Format("name {0}", i);
item.SetValue(Canvas.LeftProperty, left);
item.SetValue(Canvas.TopProperty, top);
items.Add(item);
i++;
left += 200;
top += 150;
item = (Border)XamlReader.Load(
"<Border Background=\"Yellow\" Margin=\"160, 120, 0, 0\" Height=\"120\" Width=\"160\" xmlns=\"http://schemas.microsoft.com/client/2007\"></Border>");
item.Name = string.Format("name {0}", i);
item.SetValue(Canvas.LeftProperty, left);
item.SetValue(Canvas.TopProperty, top);
items.Add(item);
i++;
left += 170;
top += 130;
item = (Border)XamlReader.Load(
"<Border Background=\"Red\" Height=\"60\" Width=\"80\" xmlns=\"http://schemas.microsoft.com/client/2007\"></Border>");
item.Name = string.Format("name {0}", i);
item.SetValue(Canvas.LeftProperty, left);
item.SetValue(Canvas.TopProperty, top);
items.Add(item);
left += 90;
top += 70;
item = (Border)XamlReader.Load(
"<Border Background=\"Blue\" Height=\"30\" Width=\"40\" xmlns=\"http://schemas.microsoft.com/client/2007\"></Border>");
item.Name = string.Format("name {0}", i);
item.SetValue(Canvas.LeftProperty, left);
item.SetValue(Canvas.TopProperty, top);
items.Add(item);
try
{
// this.customItemsCollection1.ItemsSource = items;
LayoutRoot.DataContext = items;
}
catch (Exception ex)
{
textBlock1.Text = ex.Message;
}
}
and here is the xaml:
<UserControl x:Class="ItemsControlTestProject.MainPage"
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:lh="clr-namespace:ItemsControlTestProject.Helpers"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="560" xmlns:my="clr-namespace:ItemsControlTestProject">
<UserControl.Resources>
<lh:UIElementWrapper x:Key="UIElementWrapper"/>
</UserControl.Resources>
<Grid x:Name="LayoutRoot" Background="AntiqueWhite">
<my:CustomItemsCollection Canvas.Left="0" Canvas.Top="0" Background="Coral" x:Name="customItemsCollection1" Margin="0,0,0,0" Width="532" ItemsSource="{Binding Converter={StaticResource UIElementWrapper}}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Canvas Background="LightGoldenrodYellow" Canvas.Left="0" Canvas.Top="0" Width="350" Height="350"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<ContentPresenter Content="{Binding Value}" Canvas.Left="{Binding Left}" Canvas.Top="{Binding Top}" />
</DataTemplate>
</ItemsControl.ItemTemplate>
</my:CustomItemsCollection>
<TextBlock Height="61" Name="textBlock1" Text="TextBlock" AllowDrop="True" Width="526" Canvas.Left="6" Canvas.Top="367" Margin="20,388,14,0" />
</Grid>
</UserControl>
<UserControl.Resources>
<lh:UIElementWrapper x:Key="UIElementWrapper"/>
</UserControl.Resources>
<Grid x:Name="LayoutRoot" Background="AntiqueWhite">
<my:CustomItemsCollection Canvas.Left="0" Canvas.Top="0" Background="Coral" x:Name="customItemsCollection1" Margin="0,0,0,0" Width="532" ItemsSource="{Binding Converter={StaticResource UIElementWrapper}}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Canvas Background="LightGoldenrodYellow" Canvas.Left="0" Canvas.Top="0" Width="350" Height="350"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<ContentPresenter Content="{Binding Value}" Canvas.Left="{Binding Left}" Canvas.Top="{Binding Top}" />
</DataTemplate>
</ItemsControl.ItemTemplate>
</my:CustomItemsCollection>
<TextBlock Height="61" Name="textBlock1" Text="TextBlock" AllowDrop="True" Width="526" Canvas.Left="6" Canvas.Top="367" Margin="20,388,14,0" />
</Grid>
</UserControl>
The problem is that you set the dependency property Canvas.Top prior to binding it, by adding it to the collection. Before this "Add" the object you are binding has no property Canvas.Top
Just change the order
..and it should work
/Nykkel