Hi,
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>