-1

I have an ItemsControl with an .ItemsPanel> that has a <Canvas>. What I want to do is to bind Canvas.Left and Canvas.Top from the .ItemContainerStyle> to an .ItemTemplate> <DataTemplate> that has a custom control with a read-only DependencyProperty.

Here is my XAML code that I have tried:

<ItemsControl x:Name="itemsControl" ItemsSource="{Binding CanvasObjectModels}">
    <ItemsControl.ItemsPanel>
        <ItemsPanelTemplate>
            <Canvas Width="1000" Height="1000" Background="GhostWhite"
                MouseDown="Canvas_MouseDown"/>
        </ItemsPanelTemplate>
    </ItemsControl.ItemsPanel>
    <ItemsControl.ItemContainerStyle>
        <Style TargetType="ContentPresenter">
            <Setter Property="Canvas.Left" Value="{Binding ElementName=NodeTemplate, Path=ActualOrigin.X}" />
            <Setter Property="Canvas.Top" Value="{Binding ElementName=NodeTemplate, Path=ActualOrigin.Y}" />
        </Style>
    </ItemsControl.ItemContainerStyle>
    <ItemsControl.ItemTemplate>
        <DataTemplate>
            <CanvasObject:Node x:Name="NodeTemplate" Origin="{Binding Origin}"/>
        </DataTemplate>
    </ItemsControl.ItemTemplate>
</ItemsControl>

The MouseDown event sends the Point data to the ViewModel to add to the ObservableCollection CanvasObjectModels Origin property. When the Origin is set, the <Node> will set the read-only ActualOrigin DependencyProperty. I have verified that ActualOrigin is indeed set properly. However, as this code stands right now, the <Node> is set only at 0,0 on the <Canvas>.

It would appear that a similar question was asked Wpf bind property in ItemContainerStyle to TextBlock in DataTemplate of ItemTemplate, however, this example is not the same scenario and doesn't really show how to address my concern. It does however seem to suggest that I could set the Canvas.Left and Canvas.Top properties directly in the <Node> tag with the binding but I got the same results when I tried that as well.

Also of note, I got an error when executing the code:

System.Windows.Data Error: 4 : Cannot find source for binding with reference 'ElementName=NodeTemplate'. BindingExpression:Path=ActualOrigin.X; DataItem=null; target element is 'ContentPresenter' (Name=''); target property is 'Left' (type 'Double')

Other than it cannot find the source for binding, I am uncertain what this means.

Somehow I do not think the name I applied to the <CanvasObject:Node> is being accessed by the .ItemContainerStyle> tag.

The answer to this post might shed some light to my problem: Binding an attached property to an item in ItemsControl with custom panel problem, but their scenario also is sort of different from mine because I am not using a custom panel.

Any help would be greatly appreciated.

  • 1
    Why would you not bind to x and y properties in the item viewmodel? – Andy Aug 18 '23 at 17:36
  • You could get rid of the whole trouble by deriving a class from ItemsControl that uses Node as item container. – Clemens Aug 18 '23 at 17:59
  • @Andy, I am not binding it to the viewmodel because the custom control that I made is for a drawing program. When I click the mouse down, I want that point to be at the center of the custom control so I would need the actual origin to be calculated to be different from relative origin. In other words, if I have a 50 x 50 square, if I click on coordinate 25, 25, the square will be positioned at 0,0 because 25 is half of 50. – Experiment-626 Aug 18 '23 at 18:54
  • @Clemens, perhaps if I fully understood how the ItemsControl worked, that could be done. My understanding of how an ItemsControl works is the Panel is where you want the list to be presented on, the ContainerStyle gives some inner location of the panel, and the Template is how you want your item to look. I probably just do not understand how to use the ContainerStyle. – Experiment-626 Aug 18 '23 at 18:58
  • @Experiment-626 Apart from the suggestions, without looking your actual code, we cannot identify where you made a mistake. – emoacht Aug 18 '23 at 19:35
  • The mistake is obvious. Setters in the ItemContainerStyle can not access elements in the ItemTemplate. The approach won't work, and is a bad idea in general. ItemContainerStyle and ItemTemplate should not be interdependent. – Clemens Aug 18 '23 at 19:50
  • @Clemens, how would I be able to access the read-only Dependency Property from the ItemTemplate in the ItemContainerStyle? Basically, when the Node is created from the template, I need to set the Canvas.Left and Canvas.Top based on the ActualOrigin property in Node. – Experiment-626 Aug 18 '23 at 21:03
  • Not at all, as already said. – Clemens Aug 18 '23 at 21:18
  • @Clemens, I assume that you understand what I am trying to accomplish, so if it is not possible to do it in the manner that I am trying, is there another way to accomplish my task? In general, when I assign a point to the Origin property in the ViewModel, I have it bound to the Node control to process the point and assign the real Origin (ActualOrigin) based on variable data within the node. Sometimes the quadrilateral node could be 50x50, or it could be 100x100. – Experiment-626 Aug 18 '23 at 21:43
  • You should use X and y in the item viewmodel and a converter that subtracts half width/ height of the itemcontainer. The itemcontainer will be the parent of your square or whatever you template into it. This is how I position units for our scenario editor btw. – Andy Aug 18 '23 at 23:14
  • "*is there another way to accomplish my task?*" - sure, let the view model directly provide the Left and Top values. If for whatever reason (that you have not really explained) the control *must* be involved, do what you were already told. Derive a control from ItemsControl that uses Node as its item container. See [this answer](https://stackoverflow.com/a/13887391/1136211) or [this one](https://stackoverflow.com/a/31788877/1136211) or [this one](https://stackoverflow.com/a/48588279/1136211). – Clemens Aug 19 '23 at 04:53
  • @Clemens, I thought it necessary to make a Node control because it will ultimately contain several labels that will be able to be individually moved around and turned invisible if need be, and potentially other things. Per your suggestion, I derived a new control from ItemsControl that overrode the GetContainerForItemOverride, but now I do not know how to pass data into the Node control container. I could possibly try to extract the data internally from what is populated in the derived ItemsControl, but would that be best practices, particularly regarding MVVM? – Experiment-626 Aug 22 '23 at 16:06
  • @Clemens, reviewing the source for ItemsControl, it looks like there's no need to override the GetContainerForItemOverride method if the item being added to the ItemsControl is a UIElement. [On line 1324](https://referencesource.microsoft.com/#PresentationFramework/src/Framework/System/Windows/Controls/ItemsControl.cs,1324), ItemsControl checks to see if the Items are their own containers. Unfortunately, now I cannot bind the Canvas.Left or .Top attached properties because the binding is from the context of the ItemSource and it does not look like I can access the control's properties now. – Experiment-626 Aug 22 '23 at 17:07
  • If you would override GetContainerForItemOverride, you could directly access properties of the associated item in the ItemsSource collection. – Clemens Aug 22 '23 at 17:41
  • @Clemens, you are correct that I can access properties from the observable collection if I derive a control from ItemsControl, which I did if you check one of my comments, however, I now have two issues: 1) I cannot switch to another control type that will be part of my observable collection (because it is not of Node type), and 2) I cannot get data internal to the "container" since I cannot bind to it anymore, unless I am not aware of something that I am missing. – Experiment-626 Aug 22 '23 at 20:10
  • GetContainerForItemOverride could obviously create different container element types for different types of items. And it could of course establish Bindings on the properties of the item type. Just in code, not in XAML. – Clemens Aug 23 '23 at 06:17
  • @Clemens, I don't know how to bind them in the code. I guess I would need to make a constructor that passes in all of the properties I want to bind but I do not know where I would be able to access them internally from the derived ItemsControl. How do I do this? – Experiment-626 Aug 23 '23 at 14:52
  • You guess wrong. Search StackOverflow for how to bind a property in code. – Clemens Aug 23 '23 at 15:12
  • @Clemens, I used this string to search StackOverflow: **C# wpf how to bind a property in code** and cannot find how to bind to a property in code. It would be more helpful for me and anyone else having similar issues if you could just point out where I can find the information instead of making me have to search for it. – Experiment-626 Aug 23 '23 at 15:44
  • @Andy, you said **_You should use X and y in the item viewmodel and a converter that subtracts half width/ height of the itemcontainer. The itemcontainer will be the parent of your square or whatever you template into it. This is how I position units for our scenario editor btw._**, how could I get the ActualWidth and ActualHeight of the container (Node) to set the Canvas.Left and Canvas.Top from the VM before the container is populated so I can place it where it needs to be? – Experiment-626 Aug 24 '23 at 15:48
  • Don't. Use relativesource binding for your converter parameter to search up the visual tree to the parent canvas. – Andy Aug 24 '23 at 19:33
  • @Andy, what do you mean by that? I have searched up the visual tree to find the parents in the past but I do not understand what you mean by **_Use relativesource binding for your converter parameter_**. – Experiment-626 Aug 24 '23 at 20:04
  • Didn't you try a web search? https://stackoverflow.com/questions/84278/how-do-i-use-wpf-bindings-with-relativesource – Andy Aug 25 '23 at 15:51
  • @Andy, I apologize for not realizing what you meant. I didn't realize RelativeSource was part of WPF. I will look at the link. – Experiment-626 Aug 25 '23 at 16:25
  • @Andy, I went to the link that you showed and I tried to bind RelativeSource with the examples shown, but I got an error saying that ConverterParameter cannot bind because it is not a DependencyProperty. A search on the net indicates I should use MultiBinding but when I try to send in a value with binding to RelativeSource, nothing is being passed in. I am not sure how to do what you suggest. Perhaps you could show a quick example? – Experiment-626 Aug 28 '23 at 19:51
  • @Andy, on further evaluation, I can not visualize what you are doing. I have tried so many things but cannot get the solution. I cannot access the width or height of the ItemContainer. The DataTemplate that I am using for my container is my own Custom Control called Node. I tried to FindAncestor of Node but nothing was found. I tried to FindAncestor of other components in the Node.xaml file and I still cannot find anything. I hope you can give an example. – Experiment-626 Aug 28 '23 at 22:39

0 Answers0