-2

In my XAML, I have various error messages in the following structure:

    <Border CornerRadius="3" Background="#FFF3C7C7" Margin="6" Visibility="Collapsed" Name="quick_error" Tag="err_box">
        <TextBlock Name="err_msg" Foreground="#FFFD3434" TextWrapping="Wrap" Margin="6" ></TextBlock>
    </Border>

The blocks will be identical other than Name.

I'm trying to write code to turn off all visible errors at once. To do that, I'm using a recursive function to iterate over all child elements of my main container and then look for any children from there and iterate/recurse over them as well (because the errors can be at any depth and inside any combination of parent containers).


        private void ShowError(string errName,string errMsg)
        {
            Border errBox = (Border) mainApp.FindName(errName);
            errBox.Visibility = Visibility.Visible;
            TextBlock msg = (TextBlock) errBox.FindName("err_msg");
            msg.Text = errMsg; 
        }

        private void RecurseChildren(DependencyObject child)
        {
            if (child is Border)
            {
                Border temp = (Border)child;
            
                if (temp.Tag == "err_box")
                    ShowError("quick_error", temp.Name);
            }
            for(var i = 0; i < VisualTreeHelper.GetChildrenCount(child); i++)
            {
                RecurseChildren(VisualTreeHelper.GetChild(child, i));
            }           
        }

        private void TestRecurse()
        {
            foreach (UIElement child in mainApp.Children)
            {
                RecurseChildren(child);
            }
        }

For now, the above code is designed to show a message in one error box (named "quick_error") as a test. I'm trying to get it to show the name of the borders it finds (I'm aware the names will be overwritten by the next border found, but that's enough to see it's working so that's fine).

But for some reason, nothing shows. As a test, I removed the if statement, but then the value that shows in the error box is "border" which is not the name of any border in my XAML. I have two border values, one named "temp" and one named "quick_error".

I tried showing the tagname only, but if I do that...

ShowError("quick_error", (string)temp.Tag);

... it just comes up blank. To test, I assigned a tag to my other border so both have a tag but it's still blank. So neither tag nor name are returning expected values. What's am I doing wrong?

not_a_generic_user
  • 1,906
  • 2
  • 19
  • 34
  • You should use data binding to eliminate the need to search elements. Your current solution is too complicated and not very elegant. Maybe using data validation by implementing INotifyDataErrorInfo is also an option for you. Whatever you are trying to do, your current solution is the wrong way to go, for sure. To iterate over the *complete!* visual tree to find a handful of Border elements? Bad performance, bad idea. – BionicCode May 04 '22 at 16:38
  • [How do I ask a good question?.](https://stackoverflow.com/help/how-to-ask) – BionicCode May 04 '22 at 16:39
  • INotifyDataErrorInfo is for data validation, not on-the-fly messages from what I can see. And I've read the linked guidance for asking questions and see no problem with mine. Did you have a specific critique? – not_a_generic_user May 04 '22 at 17:43
  • I'm open to other ways of approaching this problem, but none that I've seen so far work for what I'm doing or are simple enough to implement without a few more years of C# experience than I have. – not_a_generic_user May 04 '22 at 17:44
  • *"Did you have a specific critique?"* - Yes, indeed. I was trying to say that nobody can help you to guide you without knowing what you are actually trying to do. In other words, why are you doing what you are doing? What is the goal or problem you are trying to solve? I think this information is essential when asking for help, no matter the topic. Don't only explain *what* you did. Also *why*. *Why* is almost more important as it will yield the most suggestions. *What* will tie you to your current solution, even when it is a wrong one because you overlooked something. – BionicCode May 04 '22 at 19:01
  • *"I'm open to other ways of approaching this problem"* - That's good. Now you know what to do – BionicCode May 04 '22 at 19:01
  • What information were you looking for that you're missing? I have a various error blocks on the page and I want to turn them all off on a trigger of some kind. What else is needed? – not_a_generic_user May 06 '22 at 14:00
  • 1
    All I can recommend is to bind each element's Visibility to a common boolean property HasError. Use the BooleanToVisibilityConverter. – BionicCode May 06 '22 at 14:51
  • So how would I trigger if it has an error or not? – not_a_generic_user May 06 '22 at 20:13
  • The BooleanToVisibilityConverter will convert HasError to Visibility. The object, that HasError is a member of, must implement INotifyPropertyChanged or if this object is a DependencyObject, implement HasError as a dependency property. Just setup a data binding. – BionicCode May 07 '22 at 07:03
  • Right, but then how would I apply the HasError value? I would have to have a way to address those elements individually yes? So back where we started. – not_a_generic_user May 09 '22 at 14:04
  • No we're not. Maybe I didn't explained it good enough. What you need to know is how you create a data binding to a binding source that implements INotifyPropertyChanged or implements dependency properties. The data binding will update each element for you. I can give you an example. the example will be very basic. You would have to extract the information from it and apply it to your own problem. i still don't have enough information. What you share is very sparse. – BionicCode May 09 '22 at 14:40
  • To avoid wasting time: 1) you have elements that show error messages? 2) Those elements are hidden by default? 3) They all supposed be visible at the same time/simultaneously? 4) Do you use MVVM or view models? – BionicCode May 09 '22 at 14:40
  • 1) yes. 2) yes. 3) no, they're supposed to be shown when there's an error in their section/area and I want to HIDE them all on a specific trigger (usually because they're entering new input or something). 4) I'm using no such models because I'm new to C# and don't know how to use them. Hence why I need a different solution that doesn't require "learn this entire paradigm shift". Either way, I found a workable solution and posted it below. – not_a_generic_user May 11 '22 at 14:09

1 Answers1

0

I found an answer that depends only on the tags belonging to the same type of control (which in my case was fine because I was using Border for the error messages):

        public static IEnumerable<T> FindVisualChildren<T>(DependencyObject depObj) where T : DependencyObject
        {
            if (depObj == null) yield return (T)Enumerable.Empty<T>();
            for (int i = 0; i < VisualTreeHelper.GetChildrenCount(depObj); i++)
            {
                DependencyObject ithChild = VisualTreeHelper.GetChild(depObj, i);
                if (ithChild == null) continue;
                if (ithChild is T t) yield return t;
                foreach (T childOfChild in FindVisualChildren<T>(ithChild)) yield return childOfChild;
            }
        }
        private void HideErrors()
        {
            foreach (Border temp in FindVisualChildren<Border>(mainApp))
            {
                if (temp.Tag != null && temp.Tag.ToString() == "err_box")
                {
                    temp.Visibility = Visibility.Collapsed;
                }
            }
        }

Findvisualchildren is a function that I found in this question: Find all controls in WPF Window by type

not_a_generic_user
  • 1,906
  • 2
  • 19
  • 34