views:

1560

answers:

4

Ok, here's my simple scenario. I've got collection of strings that I'm binding to a TabControl as a proof of concept. As I add strings I want a new tab with the region name as the header and a ItemsControl in the Tab container. That ItemsControl should define a new region.

        <TabControl  x:Name="tabDemo" ItemsSource="{Binding DynamicRegions}" >
        <TabControl.ItemTemplate>
            <DataTemplate>
                <TextBlock Text="{Binding}" />
            </DataTemplate>
        </TabControl.ItemTemplate>
        <TabControl.ContentTemplate>
            <DataTemplate>
                <ItemsControl cal:RegionManager.RegionName="{Binding}" ItemsSource="{x:Null}">

                </ItemsControl>
            </DataTemplate>
        </TabControl.ContentTemplate>
    </TabControl>

From there I add strings to the collection. The tabs show up fine, but when I try to call

   private void AddDynamicRegion(object arg)
  {
     var newRegionName = "Region" + (DynamicRegions.Count + 1).ToString();
     DynamicRegions.Add(newRegionName);
  }

  private void AddRandomRegionContent(object arg)
  {
     if (string.IsNullOrEmpty(SelectedRegion) )
        return; 

     Debug.WriteLine("Injected "  + RegionContent + " into " + SelectedRegion);

     var newContent = new TextBlock() { Text = RegionContent };
     _regionManager.RegisterViewWithRegion(SelectedRegion,() => newContent );

     _regionManager.Regions[SelectedRegion].Activate(newContent);
  }

It either throws an exception that the region doesn't exist or an exception that creating the region failed and my ItemsControl.ItemsSource is already set. I didn't really expect this to work out of the box, but is there any way I can create dynamic regions and inject into them at run time?

Update: Calling RegisterViewWithRegion actually injects my textblock...but getting some weird behavior between tabs.

I changed it so I can choose the region and text I want to inject. It always works for the first region I create, but after that, flipping between tabs just shows the stuff I've added to the first region. Is the tab control re-using my datatemplate across multiple tabs? I've included all my code from the ViewModel. DynamicRegions is just an ObservableCollection

+2  A: 

You need to call region.Activate(stuffIJustAddedToTabsControl) on at least one of the things you add to the region. I'll get the tab control and the region in sync. Otherwise it looks all crazy and acts worse.

Anderson Imes
I get an exception, This RegionManager does not contain a Region with the name 'Region1'.Parameter name: regionName
Nick
When I look at my RegionManager in debug mode there is none of the dynamic regions I'd expect. It was my understanding that the RegionManager was a singleton and so setting the region name attached property should create new regions.
Nick
Oh... I see what you are doing. This will likely not work how you think it will... However I can come up with a demo that does what you want. Give me a little while.
Anderson Imes
I'll keep fiddling, like I said, I didn't really expect it to work, but I figured it was worth a shot.
Nick
A: 

Ok, I've got something that works, but I'd like to see what Anderson Imes comes up with.

Basically, I set all the region stuff in code when my tabItems are generated using databinding.

  private void AddDynamicRegion(object arg)
  {
     var newRegionName = "Region" + (DynamicRegions.Count + 1).ToString();
     DynamicRegions.Add(newRegionName);

     var tabItem = View.tabDemo.ItemContainerGenerator.ContainerFromIndex(DynamicRegions.Count - 1) as TabItem;
     var newRegionContainer = new ItemsControl();
     RegionManager.SetRegionName(newRegionContainer,newRegionName);
     RegionManager.SetRegionManager(newRegionContainer, _regionManager);
     tabItem.Content = newRegionContainer;


  }

So then in insert I can just do this, and everything shows up under the correct region. I wonder if there is a better way to do it though.

  private void AddRandomRegionContent(object arg)
  {

     if (string.IsNullOrEmpty(SelectedRegion) )
        return; 

     Debug.WriteLine("Injected "  + RegionContent + " into " + SelectedRegion);

     var newContent = new TextBlock() { Text = RegionContent };
     var region = _regionManager.Regions[SelectedRegion];
     if (region == null)
     {
        Debug.WriteLine("Couldn't find region");
        return;
     }

     region.Add(newContent);
     region.Activate(newContent);

  }
Nick
yeah... you definitely have to activate it... otherwise it won't show up correctly under a selector control of any kind. This was very close to what I was going to propose.
Anderson Imes
A: 

Would making the TabControl a region work for you? That way you can just add regions to that view. If those views need a region, just create a sub/nested region and view inject to that also. I really have never needed to dynamically create regions...

     <TabControl cal:RegionManager.RegionName="TabRegion" x:Name="tabDemo" ItemsSource="{Binding Something}" >
        <TabControl.ItemTemplate>
            <DataTemplate>
                <TextBlock Text="{Binding}" />
            </DataTemplate>
        </TabControl.ItemTemplate>
     </TabControl>

The StockChartsRI in Prism show how you can bind the tab's header right from your view model. They use a subclass of the TabPanel (AnimatedTabPanel), but the setup is the same.

Jeremiah Morrill
In our case we have discrete views we need to aggregate on the fly. We'll be getting the structure of aggregates from the database. So the tab control will be bound to the list of tabs, and then for each tab we'll have the metadata of which views to insert in this scenario. I understand where you're coming from, but there are some mitigating circumstances that really make dynamic regions the more attractive option, I think.
Nick
I think you are overcomplicating your issue. I have the similar setup as you described in my SL application. Settings are read over WCF to find "installed plugins". Each installed plugin get's their own tab in a tab control. For each "installed plugin", I create a ViewModel and add it's View to a Region that belongs to the TabControl. The tab control binds its header to the VM I just added. Each view that I add has it's own nested regions. The only reason to ever create a region at runtime should be for sub/nested regions.
Jeremiah Morrill
A: 

You might take a look at this article, which addresses similar issues.

David Veeneman