views:

572

answers:

3

I currently have a WPF project which has one main Window, and many UserControls which are children of this Window. Many of the children of this window are Tabs. I have successfully replaced my main Window with a User Control that implements almost exactly the same functionality as the Main Window.

Replacing the Window with a UserControl introduced one problem - currently our application determines which programming tab to display based on the parent window by using the Window.FindName method shown below. Therefore I need to replace the Application.Current.MainWindow with an appropriate description of my main user control. See the erroring C# method below and wpf instantiation of the tabs for clarification.

Note Regarding Window.FindName() method - the reason why it does not work after I replaced it with a UserControl is because the FindName method searches upwards in the visual tree, as described here.

Does anyone know how to find a user control based on the x:Name, similar to Application.Current.MainWindow ? Also, is there a better way to find the UserControl than looking for the x:Name string in case it gets renamed?

How we currently find the MainWindow - need to now find MainUserControl:

(C#)
private static void SetCurrentProgram(int num)
    {
        Window window = Application.Current.MainWindow;
        ProgrammingTab programmingTab1 = window.FindName("ProgrammingTab1") as ProgrammingTab;
        ProgrammingTab programmingTab2 = window.FindName("ProgrammingTab2") as ProgrammingTab;

        programmingTab1.Visibility = num == 1 ? Visibility.Visible : Visibility.Collapsed;
        programmingTab2.Visibility = num == 2 ? Visibility.Visible : Visibility.Collapsed;

    }

Instantiation of programming tabs.

(xaml)
<Grid>
 <ProgrammingControl:ProgrammingTab x:Name="ProgrammingTab1" Program="1" IsVisibleChanged="ProgrammingTab_IsVisibleChanged" />
 <ProgrammingControl:ProgrammingTab x:Name="ProgrammingTab2" Program="2" IsVisibleChanged="ProgrammingTab_IsVisibleChanged" />
</Grid>
+1  A: 

It sounds like your app is developed in a very WinForms-like style. To stick with that style and simply answer your question, you can FindName() to find the UserControl and again to find the ProgrammingTab, like this:

var userControl = (MyUserControl)Application.Current.MainWindow.FindName("userControlName");
var programmingTab1 = (ProgrammingTab)userControl.FindName("ProgrammingTab1");
var programmingTab2 = (ProgrammingTab)userControl.FindName("ProgrammingTab2");
...

However I would recommend you look into using WPF's advanced capabilities such as data binding. You can have a DependencyProperty "CurrentProgram" on a singleton object referenced by a static property, and simply databind Visiblity to it using a converter.

<ProgrammingTab Visibilty="{Binding CurrentProgram,
   Source={x:Static MyState.Instance},
   Converter={x:Static VisibleIfEqualConverter},
   ConverterParameter=1}" ...>
  ...

With this change, your SetCurrentProgram becomes simply:

public void SetCurrentProgram(int num)
{
   MyState.Instance.CurrentProgram = num;
}

The beauty of this technique is that any ProgrammingTab anywhere in your application will automatically appear or disappear every time MyState.Instance.CurrentProgram's vaulue changes, with no need to find them with FindName() or otherwise.

Ray Burns
I appreciate all of your insight - I'll check out your WPF suggestion. However, I found that the line *var userControl = (MyUserControl)Application.Current.MainWindow.FindName("userControlName");*ends up returning null. I confirmed the x:Name was correct. I created a new main window and instantiated the user control within the Grid of the Window. Any suggestions? I can provide more info if necessary.
CrimsonX
Looking into this further - I believe the issue has to do with one of two things: 1) The *(MyUserControl)Application.Current.MainWindow.FindName("userControlName");* somehow being in the wrong NameScope (see http://msdn.microsoft.com/en-us/library/ms746659.aspx for more information) OR 2) I'm going down the wrong path and I should be using the VisualTreeHelper: http://stackoverflow.com/questions/636383/wpf-ways-to-find-controls
CrimsonX
You say you "instantiated the user control within the Grid of the Window". Did you do this in code? If so, did you remember to call RegisterName()? It is not done for you. If you did it in XAML, are there any intervening objects that have their own NameScope?
Ray Burns
Again I want to mention: Doing this the old WinForms way feels fragile and error-prone to me. I really think you should consider using data bindings.
Ray Burns
I instantiated the user control as a child of the <Grid> in XAML, so therefore no RegisterName() method call is required. You are right that I would have to be careful about this if I had instantiated it procedurally. I will investigate why FindName is not working like I expect it to, but I was able to solve the problem procedurally (without changing databindings in XAML *yet*) per my answer.
CrimsonX
A: 

I figured out a workaround to this problem: I created a new algorithm based on another StackOverflow user's algorithm that recursively found any children of a DependencyObject. Find my solution here. If you declare the FindChild() method in my other post within public static class UIHelper {} you can then solve the problem by doing this:

  ProgrammingTab programmingTab1 = UIHelper.FindChild<ProgrammingTab>(Application.Current.MainWindow, "ProgrammingTab1");
  ProgrammingTab programmingTab2 = UIHelper.FindChild<ProgrammingTab>(Application.Current.MainWindow, "ProgrammingTab2");

This still uses procedural code instead of declarative XAML for bindings like RayBurns suggested. If it works, his solution will be much more efficient as it wont be traversing a whole tree but rather just changing the visibility of tabs based on a converter. I'll test that solution now and see how it turns out.

The reason why FindName() doesn't work properly is described in the post here.

CrimsonX