Skip to content
  • Categories
  • Recent
  • Tags
  • Popular
  • World
  • Users
  • Groups
Skins
  • Light
  • Cerulean
  • Cosmo
  • Flatly
  • Journal
  • Litera
  • Lumen
  • Lux
  • Materia
  • Minty
  • Morph
  • Pulse
  • Sandstone
  • Simplex
  • Sketchy
  • Spacelab
  • United
  • Yeti
  • Zephyr
  • Dark
  • Cyborg
  • Darkly
  • Quartz
  • Slate
  • Solar
  • Superhero
  • Vapor

  • Default (No Skin)
  • No Skin
Collapse
Code Project
  1. Home
  2. General Programming
  3. WPF
  4. Custom Navigation Control

Custom Navigation Control

Scheduled Pinned Locked Moved WPF
wpfcsharpasp-netwcfdocker
12 Posts 3 Posters 6 Views 1 Watching
  • Oldest to Newest
  • Newest to Oldest
  • Most Votes
Reply
  • Reply as topic
Log in to reply
This topic has been deleted. Only users with topic management privileges can see it.
  • K Offline
    K Offline
    Kevin Marois
    wrote on last edited by
    #1

    Using WPF .Core 6 I am trying to create this custom control[^]. I'm open to a better way if anyone has one. There is the outer Navigation Container, with Navigation Panes inside it. There can be any number of Navigation Panes. Each Navigation Pane will contain Navigation Items as links that the user can click on. What I would like to happen when the Window shows is for the container to be there, and all the panes added and showing a spinning indicator that is visible as long as the pane is loading. Each pane must load independant of the other, and each could take any amount of time to load. So far, the Navigation Container displays, and each NavigationPane shows, and the code begind for the Navigation Pane is receiving its data, but nothing shows up. And, the Spinning Indicator never goes away. The problem seems to be in the NavigationPane. The NavigationContainer is working as expected Here's what I'm getting right now.[^] I'm NOT getting any binding errors, and all relevant code is being hit - I just don't see anything. Here's the NavigationContainer's Load() method. It adds each pane first, then retrieves the data for each. This all runs, and the data is assigned to the Items DP on the NavigationPane

    private async Task Load()
    {
    if (NavigationPanes != null)
    {
    // Add a pane to the collection for each NavigationItemModel. This shows the panes
    // to the user BEFORE starting the data download
    foreach (var navigationPaneModel in NavigationPanes)
    {
    var pane = new NavigationPane
    {
    Header = navigationPaneModel.Header ?? "",
    NavigationItemType = navigationPaneModel.NavigationItemType
    };
    ContainerItems.Add(pane);
    }

                if (\_apiProxy != null)
                {
                    // Go through each pane model
                    foreach (var navigationPaneModel in NavigationPanes)
                    {
                        await Task.Run(() =>
                        {
                            // Go th
    
    P Richard DeemingR 2 Replies Last reply
    0
    • K Kevin Marois

      Using WPF .Core 6 I am trying to create this custom control[^]. I'm open to a better way if anyone has one. There is the outer Navigation Container, with Navigation Panes inside it. There can be any number of Navigation Panes. Each Navigation Pane will contain Navigation Items as links that the user can click on. What I would like to happen when the Window shows is for the container to be there, and all the panes added and showing a spinning indicator that is visible as long as the pane is loading. Each pane must load independant of the other, and each could take any amount of time to load. So far, the Navigation Container displays, and each NavigationPane shows, and the code begind for the Navigation Pane is receiving its data, but nothing shows up. And, the Spinning Indicator never goes away. The problem seems to be in the NavigationPane. The NavigationContainer is working as expected Here's what I'm getting right now.[^] I'm NOT getting any binding errors, and all relevant code is being hit - I just don't see anything. Here's the NavigationContainer's Load() method. It adds each pane first, then retrieves the data for each. This all runs, and the data is assigned to the Items DP on the NavigationPane

      private async Task Load()
      {
      if (NavigationPanes != null)
      {
      // Add a pane to the collection for each NavigationItemModel. This shows the panes
      // to the user BEFORE starting the data download
      foreach (var navigationPaneModel in NavigationPanes)
      {
      var pane = new NavigationPane
      {
      Header = navigationPaneModel.Header ?? "",
      NavigationItemType = navigationPaneModel.NavigationItemType
      };
      ContainerItems.Add(pane);
      }

                  if (\_apiProxy != null)
                  {
                      // Go through each pane model
                      foreach (var navigationPaneModel in NavigationPanes)
                      {
                          await Task.Run(() =>
                          {
                              // Go th
      
      P Offline
      P Offline
      Pete OHanlon
      wrote on last edited by
      #2

      My immediate thought is that the problem is in the task continuation. Have you checked the result of the FirstOrDefault, to make sure that it is actually finding the container?

      Advanced TypeScript Programming Projects

      1 Reply Last reply
      0
      • K Kevin Marois

        Using WPF .Core 6 I am trying to create this custom control[^]. I'm open to a better way if anyone has one. There is the outer Navigation Container, with Navigation Panes inside it. There can be any number of Navigation Panes. Each Navigation Pane will contain Navigation Items as links that the user can click on. What I would like to happen when the Window shows is for the container to be there, and all the panes added and showing a spinning indicator that is visible as long as the pane is loading. Each pane must load independant of the other, and each could take any amount of time to load. So far, the Navigation Container displays, and each NavigationPane shows, and the code begind for the Navigation Pane is receiving its data, but nothing shows up. And, the Spinning Indicator never goes away. The problem seems to be in the NavigationPane. The NavigationContainer is working as expected Here's what I'm getting right now.[^] I'm NOT getting any binding errors, and all relevant code is being hit - I just don't see anything. Here's the NavigationContainer's Load() method. It adds each pane first, then retrieves the data for each. This all runs, and the data is assigned to the Items DP on the NavigationPane

        private async Task Load()
        {
        if (NavigationPanes != null)
        {
        // Add a pane to the collection for each NavigationItemModel. This shows the panes
        // to the user BEFORE starting the data download
        foreach (var navigationPaneModel in NavigationPanes)
        {
        var pane = new NavigationPane
        {
        Header = navigationPaneModel.Header ?? "",
        NavigationItemType = navigationPaneModel.NavigationItemType
        };
        ContainerItems.Add(pane);
        }

                    if (\_apiProxy != null)
                    {
                        // Go through each pane model
                        foreach (var navigationPaneModel in NavigationPanes)
                        {
                            await Task.Run(() =>
                            {
                                // Go th
        
        Richard DeemingR Offline
        Richard DeemingR Offline
        Richard Deeming
        wrote on last edited by
        #3

        Kevin Marois wrote:

        Each pane must load independant of the other

        That doesn't seem to tally with:

        Kevin Marois wrote:

        foreach (var navigationPaneModel in NavigationPanes)
        {
        await Task.Run(() =>
        {
        // Go the the back end & get the data
        return _apiProxy.GetNavigationItems(navigationPaneModel.NavigationItemType);
        }).ContinueWith(task =>
        {
        App.Current.Dispatcher.BeginInvoke(() =>
        {
        // Find the container for the data
        var container = ContainerItems.FirstOrDefault(x => x.NavigationItemType == navigationPaneModel.NavigationItemType);
        if (container != null && task.Result != null)
        {
        // Assign the data to it
        container.Items = new ObservableCollection(task.Result);
        }
        });
        });
        }

        Your loop kicks off a task to load each pane, then waits for that task to complete before trying to load the next pane. Try extracting the "load a pane" code to a separate method:

        private async Task LoadPane(NavigationPane navigationPaneModel)
        {
        var result = await Task.Run(() => _apiProxy.GetNavigationItems(navigationPaneModel.NavigationItemType);
        if (result is null) return;

        var container = ContainerItems.FirstOrDefault(x => x.NavigationItemType == navigationPaneModel.NavigationItemType);
        if (container is null) return;
        
        container.Items = new ObservableCollection<NavigationEntity>(result);
        

        }

        Then change the loop to:

        List<Task> tasks = new List<Task>(NavigationPanes.Count);
        foreach (var navigationPaneModel in NavigationPanes)
        {
        tasks.Add(LoadPane(navigationPaneModel));
        }

        await Task.WhenAll(tasks);

        Kevin Marois wrote:

        control.IsBusyVisible = Visibility.Collapsed; //<==== THIS IS BEING HIT, BUT THE INDICATOR STILL SHOWS. AND NO DATA ITEMS APPEAR

        You've declared the IsBusyVisible property as a regular property, but your NavigationPane class is a DependencyObject. The WPF binding system will only observe changes to DependencyProperty properties on a DependencyObject-derived class. Change the property to a dependency property:

        publ

        "These people looked deep within my soul and assigned me a number based on the order in which I joined" - Homer

        K 1 Reply Last reply
        0
        • Richard DeemingR Richard Deeming

          Kevin Marois wrote:

          Each pane must load independant of the other

          That doesn't seem to tally with:

          Kevin Marois wrote:

          foreach (var navigationPaneModel in NavigationPanes)
          {
          await Task.Run(() =>
          {
          // Go the the back end & get the data
          return _apiProxy.GetNavigationItems(navigationPaneModel.NavigationItemType);
          }).ContinueWith(task =>
          {
          App.Current.Dispatcher.BeginInvoke(() =>
          {
          // Find the container for the data
          var container = ContainerItems.FirstOrDefault(x => x.NavigationItemType == navigationPaneModel.NavigationItemType);
          if (container != null && task.Result != null)
          {
          // Assign the data to it
          container.Items = new ObservableCollection(task.Result);
          }
          });
          });
          }

          Your loop kicks off a task to load each pane, then waits for that task to complete before trying to load the next pane. Try extracting the "load a pane" code to a separate method:

          private async Task LoadPane(NavigationPane navigationPaneModel)
          {
          var result = await Task.Run(() => _apiProxy.GetNavigationItems(navigationPaneModel.NavigationItemType);
          if (result is null) return;

          var container = ContainerItems.FirstOrDefault(x => x.NavigationItemType == navigationPaneModel.NavigationItemType);
          if (container is null) return;
          
          container.Items = new ObservableCollection<NavigationEntity>(result);
          

          }

          Then change the loop to:

          List<Task> tasks = new List<Task>(NavigationPanes.Count);
          foreach (var navigationPaneModel in NavigationPanes)
          {
          tasks.Add(LoadPane(navigationPaneModel));
          }

          await Task.WhenAll(tasks);

          Kevin Marois wrote:

          control.IsBusyVisible = Visibility.Collapsed; //<==== THIS IS BEING HIT, BUT THE INDICATOR STILL SHOWS. AND NO DATA ITEMS APPEAR

          You've declared the IsBusyVisible property as a regular property, but your NavigationPane class is a DependencyObject. The WPF binding system will only observe changes to DependencyProperty properties on a DependencyObject-derived class. Change the property to a dependency property:

          publ

          K Offline
          K Offline
          Kevin Marois
          wrote on last edited by
          #4

          Thanks. This make much more sense. So I extracted all the Nav stuff into a demo project to get it out of my app, and I still can get this to work. It looks like the panes never get added. I put it in a repo here[^]. I would appreciate more help if you wouldn't mind.

          If it's not broken, fix it until it is. Everything makes sense in someone's mind. Ya can't fix stupid.

          P Richard DeemingR 2 Replies Last reply
          0
          • K Kevin Marois

            Thanks. This make much more sense. So I extracted all the Nav stuff into a demo project to get it out of my app, and I still can get this to work. It looks like the panes never get added. I put it in a repo here[^]. I would appreciate more help if you wouldn't mind.

            If it's not broken, fix it until it is. Everything makes sense in someone's mind. Ya can't fix stupid.

            P Offline
            P Offline
            Pete OHanlon
            wrote on last edited by
            #5

            I was going to take a look, but it looks like your repo is private.

            Advanced TypeScript Programming Projects

            K 1 Reply Last reply
            0
            • P Pete OHanlon

              I was going to take a look, but it looks like your repo is private.

              Advanced TypeScript Programming Projects

              K Offline
              K Offline
              Kevin Marois
              wrote on last edited by
              #6

              Oops. It's public now. Thanks Pete!

              If it's not broken, fix it until it is. Everything makes sense in someone's mind. Ya can't fix stupid.

              P 1 Reply Last reply
              0
              • K Kevin Marois

                Oops. It's public now. Thanks Pete!

                If it's not broken, fix it until it is. Everything makes sense in someone's mind. Ya can't fix stupid.

                P Offline
                P Offline
                Pete OHanlon
                wrote on last edited by
                #7

                I'll have a look in the morning.

                Advanced TypeScript Programming Projects

                K 1 Reply Last reply
                0
                • P Pete OHanlon

                  I'll have a look in the morning.

                  Advanced TypeScript Programming Projects

                  K Offline
                  K Offline
                  Kevin Marois
                  wrote on last edited by
                  #8

                  Hey Pete, just following up to see if had a chance to look at this?

                  If it's not broken, fix it until it is. Everything makes sense in someone's mind. Ya can't fix stupid.

                  P 1 Reply Last reply
                  0
                  • K Kevin Marois

                    Hey Pete, just following up to see if had a chance to look at this?

                    If it's not broken, fix it until it is. Everything makes sense in someone's mind. Ya can't fix stupid.

                    P Offline
                    P Offline
                    Pete OHanlon
                    wrote on last edited by
                    #9

                    I have spent some time digging into your sample and I am at a loss. I can't see anything obvious in there, and it's an absolute stumper.

                    Advanced TypeScript Programming Projects

                    1 Reply Last reply
                    0
                    • K Kevin Marois

                      Thanks. This make much more sense. So I extracted all the Nav stuff into a demo project to get it out of my app, and I still can get this to work. It looks like the panes never get added. I put it in a repo here[^]. I would appreciate more help if you wouldn't mind.

                      If it's not broken, fix it until it is. Everything makes sense in someone's mind. Ya can't fix stupid.

                      Richard DeemingR Offline
                      Richard DeemingR Offline
                      Richard Deeming
                      wrote on last edited by
                      #10

                      The first obvious issue: the ContainerItems property is not a dependency property. As I said, for a DependencyObject-derived class, WPF will only observe property changes for dependency properties.

                      public static readonly DependencyProperty ContainerItemsProperty
                      = DependencyProperty.Register("ContainerItems",
                      typeof(List),
                      typeof(NavigationContainer),
                      new PropertyMetadata(null));

                      public List ContainerItems
                      {
                      get { return (List)GetValue(ContainerItemsProperty); }
                      set { SetValue(ContainerItemsProperty, value); }
                      }

                      Next problem: since you're using a List<T> rather than an ObservableCollection<T>, WPF will only notice when you set the property. Since you do that before adding any items to the list, WPF will never notice the panes being loaded. Change the code to set the property after populating the list:

                      private async Task Load()
                      {
                      if (NavigationPanes != null)
                      {
                      var items = new List();

                          List tasks = new List(NavigationPanes.Count);
                          foreach (var navigationPaneModel in NavigationPanes)
                          {
                              tasks.Add(LoadPane(navigationPaneModel, items));
                          }
                      
                          await Task.WhenAll(tasks);
                      	ContainerItems = items;
                      }
                      

                      }

                      private async Task LoadPane(NavigationPaneModel navigationPaneModel, List containerItems)

                      With those changes in place (and dropping the Thread.Sleep values so it loads in a reasonable time), I now get four expanders in the list. However, although they have different headers, none of them contain any data. Looking in the Generic.xaml file, you're binding the Header property, but not the Items property:

                      Once you add Items="{Binding Items}" to that, the listboxes are now populated.


                      "These people looked deep within my soul and assigned me a number based on the order in which I joined." - Homer

                      "These people looked deep within my soul and assigned me a number based on the order in which I joined" - Homer

                      K 2 Replies Last reply
                      0
                      • Richard DeemingR Richard Deeming

                        The first obvious issue: the ContainerItems property is not a dependency property. As I said, for a DependencyObject-derived class, WPF will only observe property changes for dependency properties.

                        public static readonly DependencyProperty ContainerItemsProperty
                        = DependencyProperty.Register("ContainerItems",
                        typeof(List),
                        typeof(NavigationContainer),
                        new PropertyMetadata(null));

                        public List ContainerItems
                        {
                        get { return (List)GetValue(ContainerItemsProperty); }
                        set { SetValue(ContainerItemsProperty, value); }
                        }

                        Next problem: since you're using a List<T> rather than an ObservableCollection<T>, WPF will only notice when you set the property. Since you do that before adding any items to the list, WPF will never notice the panes being loaded. Change the code to set the property after populating the list:

                        private async Task Load()
                        {
                        if (NavigationPanes != null)
                        {
                        var items = new List();

                            List tasks = new List(NavigationPanes.Count);
                            foreach (var navigationPaneModel in NavigationPanes)
                            {
                                tasks.Add(LoadPane(navigationPaneModel, items));
                            }
                        
                            await Task.WhenAll(tasks);
                        	ContainerItems = items;
                        }
                        

                        }

                        private async Task LoadPane(NavigationPaneModel navigationPaneModel, List containerItems)

                        With those changes in place (and dropping the Thread.Sleep values so it loads in a reasonable time), I now get four expanders in the list. However, although they have different headers, none of them contain any data. Looking in the Generic.xaml file, you're binding the Header property, but not the Items property:

                        Once you add Items="{Binding Items}" to that, the listboxes are now populated.


                        "These people looked deep within my soul and assigned me a number based on the order in which I joined." - Homer

                        K Offline
                        K Offline
                        Kevin Marois
                        wrote on last edited by
                        #11

                        It's working now. Thanks Richard.

                        If it's not broken, fix it until it is. Everything makes sense in someone's mind. Ya can't fix stupid.

                        1 Reply Last reply
                        0
                        • Richard DeemingR Richard Deeming

                          The first obvious issue: the ContainerItems property is not a dependency property. As I said, for a DependencyObject-derived class, WPF will only observe property changes for dependency properties.

                          public static readonly DependencyProperty ContainerItemsProperty
                          = DependencyProperty.Register("ContainerItems",
                          typeof(List),
                          typeof(NavigationContainer),
                          new PropertyMetadata(null));

                          public List ContainerItems
                          {
                          get { return (List)GetValue(ContainerItemsProperty); }
                          set { SetValue(ContainerItemsProperty, value); }
                          }

                          Next problem: since you're using a List<T> rather than an ObservableCollection<T>, WPF will only notice when you set the property. Since you do that before adding any items to the list, WPF will never notice the panes being loaded. Change the code to set the property after populating the list:

                          private async Task Load()
                          {
                          if (NavigationPanes != null)
                          {
                          var items = new List();

                              List tasks = new List(NavigationPanes.Count);
                              foreach (var navigationPaneModel in NavigationPanes)
                              {
                                  tasks.Add(LoadPane(navigationPaneModel, items));
                              }
                          
                              await Task.WhenAll(tasks);
                          	ContainerItems = items;
                          }
                          

                          }

                          private async Task LoadPane(NavigationPaneModel navigationPaneModel, List containerItems)

                          With those changes in place (and dropping the Thread.Sleep values so it loads in a reasonable time), I now get four expanders in the list. However, although they have different headers, none of them contain any data. Looking in the Generic.xaml file, you're binding the Header property, but not the Items property:

                          Once you add Items="{Binding Items}" to that, the listboxes are now populated.


                          "These people looked deep within my soul and assigned me a number based on the order in which I joined." - Homer

                          K Offline
                          K Offline
                          Kevin Marois
                          wrote on last edited by
                          #12

                          Richard, after implementing your changes, the loading works fine, but I now have this problem[^] with the expander headers. I would appreciate your input.

                          If it's not broken, fix it until it is. Everything makes sense in someone's mind. Ya can't fix stupid.

                          1 Reply Last reply
                          0
                          Reply
                          • Reply as topic
                          Log in to reply
                          • Oldest to Newest
                          • Newest to Oldest
                          • Most Votes


                          • Login

                          • Don't have an account? Register

                          • Login or register to search.
                          • First post
                            Last post
                          0
                          • Categories
                          • Recent
                          • Tags
                          • Popular
                          • World
                          • Users
                          • Groups