Page 1 of 1

Referencing vectors as a bindable resource

Posted: 27 Jan 2020, 08:11
by jazza
I am currently trying to implement a view that displays a list of buttons based on bound data from an observable collection. The image for each button is defined in the application resource dictionary as follows.
<Canvas x:Key="Btn_A" Width="136" Height="136" x:Shared="false">
        <Path Fill="#ffffffff" Data="F1 M 0.000,68.276 C 0.000,61.981 0.812,55.922 2.436,50.102 C 4.059,44.282 6.357,38.848 9.331,33.800 C 12.303,28.754 15.862,24.159 20.010,20.012 C 24.157,15.864 28.766,12.305 33.837,9.331 C 38.908,6.359 44.354,4.061 50.175,2.438 C 55.995,0.814 62.054,0.000 68.349,0.000 C 74.645,0.000 80.702,0.814 86.523,2.438 C 92.343,4.061 97.777,6.359 102.824,9.331 C 107.870,12.305 112.479,15.864 116.651,20.012 C 120.822,24.159 124.382,28.754 127.330,33.800 C 130.278,38.848 132.564,44.282 134.188,50.102 C 135.811,55.922 136.623,61.981 136.623,68.276 C 136.623,74.571 135.811,80.630 134.188,86.450 C 132.564,92.270 130.278,97.717 127.330,102.787 C 124.382,107.859 120.822,112.469 116.651,116.614 C 112.479,120.762 107.870,124.321 102.824,127.293 C 97.777,130.267 92.343,132.564 86.523,134.188 C 80.702,135.812 74.645,136.623 68.349,136.623 C 62.054,136.623 55.995,135.812 50.175,134.188 C 44.354,132.564 38.908,130.267 33.837,127.293 C 28.766,124.321 24.157,120.762 20.010,116.614 C 15.862,112.469 12.303,107.859 9.331,102.787 C 6.357,97.717 4.059,92.270 2.436,86.450 C 0.812,80.630 0.000,74.571 0.000,68.276 Z"/>
        <Path Fill="#ff221e1f" Data="F1 M 54.736,77.581 L 81.888,77.581 L 68.312,40.130 L 54.736,77.581 Z M 61.758,22.574 L 74.866,22.574 L 106.231,108.010 L 93.006,108.010 L 86.336,89.752 L 50.289,89.752 L 43.735,108.010 L 30.392,108.010 L 61.758,22.574 Z"/>
    </Canvas>
Following is a snippet from the XAML view that will reference this resource inside a control template.
<!-- Defines the custom layout of a tooltip button -->
                <ControlTemplate x:Key="Template_Button_Tooltip" TargetType="Button">
                    <StackPanel Style="{StaticResource Style_TooltipRoot}">
                        <Viewbox Name="Viewbox" Style="{StaticResource Style_TooltipIcon}">
                            <!-- This control appears to be the only way to instantiate a static resource -->
                            <ContentControl Content="{Binding Button, Converter={StaticResource buttonConverter}}"/>
                        </Viewbox>
                    </StackPanel>
                </ControlTemplate>

                <!-- Assigns a visual representation to each instance of a tooltip in the tooltips collection -->
                <DataTemplate x:Key="TooltipsItemTemplate">
                    <Button Template="{StaticResource Template_Button_Tooltip}"/>
                </DataTemplate>
            </Grid.Resources>

            <ItemsControl ItemsSource="{Binding Tooltips}" Focusable="True" Template="{StaticResource TooltipListTemplate}" ItemTemplate="{StaticResource TooltipsItemTemplate}"/>
I have also created a converter that takes an enumerated value for a button from the view model and then searches the application resource dictionary for a matching resource. The pointer to this resource is returned to the control template and the button is then rendered correctly.

There are issues however when I destroy/re-create the view or modify the observable collection. Re-creating the view results in a stack overflow that seemingly originates from an 'AncestorChanged' callback being called infinitely. Clearly I shouldn't be passing a pointer to a static resource to several widgets. I suppose I should be cloning the resource and then passing it to the control template but its not clear to me how to achieve that either.

Is there a better way to reference my vector resources?

Re: Referencing vectors as a bindable resource

Posted: 28 Jan 2020, 03:58
by jazza
I've managed to find a solution that allows me to clone the vector resource as follows.
Noesis::Ptr<Noesis::BaseComponent> CloneResource(Noesis::FrameworkElement* resource)
{
	// NOTE: This function assumes the following layout for each resource
	//    <Canvas x:Key="Btn_A" Width="136" Height="136" x:Shared="false">
        //        <Path Fill="#ffffffff" Data="F1 M 0.000,68.276 C 0.000,61.981 0.812, Z"/>
	//        <!-- Additional paths -->
        //    </Canvas>

	Noesis::Ptr<Noesis::Canvas> clone = Noesis::MakePtr<Noesis::Canvas>();
	clone->SetWidth(resource->GetWidth());
	clone->SetHeight(resource->GetHeight());
	
	int childrenCount = Noesis::VisualTreeHelper::GetChildrenCount(resource);
	if (childrenCount > 0)
	{
		for (int i = 0; i < childrenCount; i++)
		{
			auto child = (Noesis::Path*)Noesis::VisualTreeHelper::GetChild(resource, i);
			
			auto path = Noesis::MakePtr<Noesis::Path>();
			path->SetData(child->GetData()->Clone());
			path->SetFill(child->GetFill());
			clone->GetChildren()->Add(path);
		}
	}

	return clone;
}
While this approach solves my issue, it is obviously very expensive. If anyone has any advice on a better approach to using a data bound vector resource in a control template, it would be much appreciated.

Re: Referencing vectors as a bindable resource

Posted: 29 Jan 2020, 10:20
by sfernandez
If you want to share a visual tree among several controls you should use a ControlTemplate/DataTemplate. That will do the clone part automatically when applied to the control.
So you can have defined ControlTemplates in your dictionary and instead of setting the Content property set the Template property:
<ControlTemplate x:Key="Btn_A" TargetType="Control">
    <Canvas Width="136" Height="136">
        <Path Fill="#ffffffff" Data="F1 M 0.000,68.276 C 0.000,61.981 0.812,55.922 2.436,50.102 C 4.059,44.282 6.357,38.848 9.331,33.800 C 12.303,28.754 15.862,24.159 20.010,20.012 C 24.157,15.864 28.766,12.305 33.837,9.331 C 38.908,6.359 44.354,4.061 50.175,2.438 C 55.995,0.814 62.054,0.000 68.349,0.000 C 74.645,0.000 80.702,0.814 86.523,2.438 C 92.343,4.061 97.777,6.359 102.824,9.331 C 107.870,12.305 112.479,15.864 116.651,20.012 C 120.822,24.159 124.382,28.754 127.330,33.800 C 130.278,38.848 132.564,44.282 134.188,50.102 C 135.811,55.922 136.623,61.981 136.623,68.276 C 136.623,74.571 135.811,80.630 134.188,86.450 C 132.564,92.270 130.278,97.717 127.330,102.787 C 124.382,107.859 120.822,112.469 116.651,116.614 C 112.479,120.762 107.870,124.321 102.824,127.293 C 97.777,130.267 92.343,132.564 86.523,134.188 C 80.702,135.812 74.645,136.623 68.349,136.623 C 62.054,136.623 55.995,135.812 50.175,134.188 C 44.354,132.564 38.908,130.267 33.837,127.293 C 28.766,124.321 24.157,120.762 20.010,116.614 C 15.862,112.469 12.303,107.859 9.331,102.787 C 6.357,97.717 4.059,92.270 2.436,86.450 C 0.812,80.630 0.000,74.571 0.000,68.276 Z"/>
        <Path Fill="#ff221e1f" Data="F1 M 54.736,77.581 L 81.888,77.581 L 68.312,40.130 L 54.736,77.581 Z M 61.758,22.574 L 74.866,22.574 L 106.231,108.010 L 93.006,108.010 L 86.336,89.752 L 50.289,89.752 L 43.735,108.010 L 30.392,108.010 L 61.758,22.574 Z"/>
    </Canvas>
</ControlTemplate>
<ControlTemplate x:Key="Template_Button_Tooltip" TargetType="Button">
  <StackPanel Style="{StaticResource Style_TooltipRoot}">
    <Viewbox Name="Viewbox" Style="{StaticResource Style_TooltipIcon}">
      <Control Template="{Binding Button, Converter={StaticResource buttonConverter}}"/>
    </Viewbox>
  </StackPanel>
</ControlTemplate>
Hope this helps.

Re: Referencing vectors as a bindable resource

Posted: 31 Jan 2020, 05:53
by jazza
That sounds like a much better solution! Thanks!

Re: Referencing vectors as a bindable resource

Posted: 31 Jan 2020, 13:01
by sfernandez
Setting this as solved then.