0

I have a Canvas with the following XAML definition:

<Canvas Height="201" VerticalAlignment="Top"
        Name="myCanvas"
        KeyDown="KeyEvent" >
    <local:MyGlyphsElement x:Name="mge" />
    <Line Name="myLine" Stroke="Blue" StrokeThickness="2"></Line>
</Canvas>

In the code-behind file for the MyGlyphsElement control, how can I access myLine and myCanvas? (MyGlyphsElement is derived from FrameworkElement.)

My purpose is to be able to add controls at runtime to myCanvas children as well as manipulate myLine properties such as stroke width, etc.

EDIT:

public partial class MyGlyphsElement: FrameworkElement
    protected override void OnRender(DrawingContext drawingContext)
    {
        base.OnRender(drawingContext);  // Good practice.
        ...
        Canvas cp = (Canvas)this.Parent;
        // Now what? How do I access myLine?
Sabuncu
  • 5,095
  • 5
  • 55
  • 89
  • See [My Example](http://stackoverflow.com/a/15580293/643085) on how to do this using proper WPF techniques such as XAML, DataBinding, ItemsControls and DataTemplates as opposed to procedural code. – Federico Berasategui Jun 25 '14 at 16:57
  • HighCore is right, but besides that, FrameworkElement has a `Parent` property that you could cast to Canvas. Canvas in turn has (as any other Panel) a `Children` property. – Clemens Jun 25 '14 at 16:59
  • @Clemens: When I used the parent property, I received an "enumaration error/collection changed" exception. – Sabuncu Jun 25 '14 at 17:03
  • You should perhaps add the relevant code to your question. Otherwise it's hard to tell what's going on. – Clemens Jun 25 '14 at 17:05
  • @HighCore: You have helped me before, and I have dissected and studied many of your solutions, you are a source of knowledge for me. But sometimes the MVVM pattern is an overkill, and one just wants a quick solution. – Sabuncu Jun 25 '14 at 17:05
  • @Sabuncu In my opinion, in WPF, a "quick" solution is one that involves proper DataBinding. Manipulating UI elements in procedural code will only cause headache due to all sorts of unexpected behavior, weird exceptions and whatnot. This will only bring you unnecessary delay and you will end up wasting more time fighting the Framework than anything. A proper DataBinding-based solution is clean, beautiful, reusable, scalable, and **just works** without problems... – Federico Berasategui Jun 25 '14 at 17:10
  • `var line = cp.Children.OfType().FirstOrDefault();` or `var line = cp.Children.OfType().FirstOrDefault(l => l.Name == "myLine");` if there are other Lines before `myLine`. – Clemens Jun 25 '14 at 17:18
  • @Clemens - I used the `FirstOrDefault()` method, no exceptions this time, but the line is not visible. Would it be because of being called from within OnRender()? – Sabuncu Jun 25 '14 at 17:28
  • @Clemens - Further information - if I resize the canvas, the line suddenly becomes visible, and stays visible. But it's initially not visible. – Sabuncu Jun 25 '14 at 17:31
  • @HighCore: How can I debug the control not initially showing problem using Snoop? Any idea? – Sabuncu Jun 25 '14 at 18:07
  • @Clemens: Thank you for your comment above. I will have to study the second approach using the lambda expression to understand how it works. – Sabuncu Jun 25 '14 at 18:29

1 Answers1

1

Make some nifty extensions for parsing the Visual Tree, like so (this will let you get an element by a specified name):

public static class DependentObjectExtensions
{
    public static IEnumerable<DependencyObject> GetChildren(this DependencyObject parent)
    {
        for (int i = 0, length = VisualTreeHelper.GetChildrenCount(parent); i < length; i++)
        {
            yield return VisualTreeHelper.GetChild(parent, i);
        }
    }

    public static T FindChild<T>(this DependencyObject parent, string name, bool drillDown = false)
        where T : FrameworkElement
    {
        if(parent != null)
        {
            var elements = parent.GetChildren().OfType<T>();
            return drillDown
                ? elements.Select(x => FindChild<T>(x, name, true)).FirstOrDefault(x => x != null)
                : elements.FirstOrDefault(x => x.Name == name);
        }
        return null;
    }
}

Then, change your MyGlyphsElement.OnRender to look like the following:

    protected override void OnRender(DrawingContext drawingContext)
    {
        base.OnRender(drawingContext);

        var myLine = Parent.FindChild<Line>("myLine");
        //TODO: Do stuff with your line.
    } 

If you need further clarifications, let me know and I will edit my response according to your feedback.

Tyler Kendrick
  • 418
  • 6
  • 18
  • +1 Thanks so much, your code works and provides me w/ a reference to the `myLine` object. Cool solution. However, and I know this is a separate problem, the `myLine` control does not display until the window is resized. Would you know why? – Sabuncu Jun 25 '14 at 17:57
  • If you are making changes to `myLine` in `OnRender` and that doesn't refresh until you resize, try moving the changes to `myLine` above `base.OnRender`. Alternatively, if that doesn't work, I suspect that some logic in your `KeyEvent` is effecting Render. If this is the case, I would be happy to help if you post the logic in there. Otherwise, please don't forget to mark the answer as resolved ;) – Tyler Kendrick Jun 25 '14 at 18:21
  • Thanks. Moving the code to above `base.OnRender()` did not help. Will have to post a separate question later, I am too tired right now. Hope the new question will get your attention. – Sabuncu Jun 25 '14 at 18:28
  • 1
    Tyler, I have posted the new question: http://stackoverflow.com/questions/24438483/line-is-not-displayed-on-startup-but-becomes-visible-on-resize-wpf FYI, thanks for your help w/ this question. – Sabuncu Jun 26 '14 at 19:22