Prism RegionAdapter - Removing then Adding View - wpf

I have a prism/wpf/mef solution that contains an AvalonDock. I created a RegionAdapterBase<Pane> class that handles creating and removing the Panes from AvalonDock.
Heres the problem I'm running into:
I click a button in my menu and a view is registered with a region and displayed in my DocumentPane
I click the close button in AvalonDock to close the tab and remove the view
I click the same menu button to add it back again
I receive the error:
"Specified element is already the
logical child of another element.
Disconnect it first."
So... this tells me that something is lingering that I need to remove, but I cannot figure out where it is. Heres some code from my RegionAdapter:
private void OnViewsCollectionChanged(object sender, NotifyCollectionChangedEventArgs e, IRegion region, Pane regionTarget)
{
if (e.Action == NotifyCollectionChangedAction.Add)
{
foreach (object item in e.NewItems)
{
UIElement view = item as UIElement;
if (view is ITabViewInfo)
{
if (view != null)
{
DockableContent newContentPane = new DockableContent()
{
Content = item,
Title = ((ITabViewInfo)view).TabViewTitle,
Icon = new Image()
{
Source = new BitmapImage(((ITabViewInfo)view).TabViewIcon)
}.Source,
IsCloseable = ((ITabViewInfo)view).IsCloseable,
HideOnClose = ((ITabViewInfo)view).IsHideOnClose
};
newContentPane.Closed += (contentPaneSender, args) =>
{
Debug.WriteLine("Removing view from region", "Prism");
region.Remove(item);
};
regionTarget.Items.Add(newContentPane);
newContentPane.Activate();
}
}
}
} else if (e.Action == NotifyCollectionChangedAction.Remove) {
regionTarget.Items.Clear();
}
}
From my debug lines, the DocumentPane and region views are properly being destroyed... when I click to add the item back to the view, I get the above error message on the line that does:
Content = item,
Heres the code from my module that runs when the menu button is pressed:
if (_regionManager.Regions["MainRegion"].Views.Any(m => m.GetType() == typeof(Views.ClassicFrontierView)))
{
Debug.WriteLine(_regionManager.Regions["MainRegion"].Views.Count());
}
else
{
Debug.WriteLine("Adding view to region", "Prism");
_regionManager.RegisterViewWithRegion("MainRegion", typeof(Views.ClassicFrontierView));
}
Any idea what I'm missing?

Do you create a new View each time or you trying to show existing View several times? If second is correct I would try this:
else if (e.Action == NotifyCollectionChangedAction.Remove) {
foreach (DockableContent content in regionTarget.Items)
content.Content = null;
regionTarget.Items.Clear();
}

Instead of handling the Closed event (which may have lost a reference to the underlying view), I handle the Closing event.
This worked, however, when I tried to re-open the tab, it was displaying the same instance. After reading this In Composite WPF (Prism), what is the difference between IRegion.Add and IRegionManager.RegisterViewWithRegion? I changed this:
_regionManager.RegisterViewWithRegion("MainRegion", typeof(Views.ClassicFrontierView));
to this:
_regionManager.Regions["MainRegion"].Add(new Classic.Views.ClassicFrontierView());
I still have to do some research with Prism / avalondock to make sure there will be no memory leaks, but as of now it appears to be working.

You likely need to remove it from regionTarget as well.
You can use Snoop to see what hasn't been removed from the Visual Tree and then attempt to find which container you need to remove your element from. Other possibilities are things like an unfrozen Icon image, etc.

Related

Detect when a row is edited in a DataGrid

I've been trying to google this but have been unable to find a solution that works for me.
I have a DataGrid that is displaying some info from a SQL table that the client dosn't know about.
The client just sends a request to the server and gets a List<SomeClass> as a response that it then displays in a DataGrid.
I need to detect when the user makes change to a row and I need the new values that the user entered.
Currently I'm using RowEditEnding event. And the method that handles this event can then:
private void editRowEventHandler(object sender, DataGridRowEditEndingEventArgs e)
{
SomeClass sClass = e.Row.DataContext as SomeClass;
// Send sClass to the server to be saved in the database...
}
This gives me the row that was being edited. But it gives me the row before the changes, and I'm unable to figure out how to get the row after the changes happen.
Is there anyone here that knows how I can do this or can point me in a direction where I might be able to find out?
See the discussion here, to avoid reading out cell-by-cell.
private void OnRowEditEnding(object sender, DataGridRowEditEndingEventArgs e)
{
DataGrid dataGrid = sender as DataGrid;
if (e.EditAction == DataGridEditAction.Commit) {
ListCollectionView view = CollectionViewSource.GetDefaultView(dataGrid.ItemsSource) as ListCollectionView;
if (view.IsAddingNew || view.IsEditingItem) {
this.Dispatcher.BeginInvoke(new DispatcherOperationCallback(param =>
{
// This callback will be called after the CollectionView
// has pushed the changes back to the DataGrid.ItemSource.
// Write code here to save the data to the database.
return null;
}), DispatcherPriority.Background, new object[] { null });
}
}
}
In your case, you are trying to detect the change in object. It comes down to the properties of the SomeClass, thus you need to focus on "Cell" instead of "Row"
Assuming your datagrid is resultGrid, i come up with the below code:
resultGrid.CellEditEnding += resultGrid_CellEditEnding;
void resultGrid_CellEditEnding(object sender, DataGridCellEditEndingEventArgs e)
{
var yourClassInstance = e.EditingElement.DataContext;
var editingTextBox = e.EditingElement as TextBox;
var newValue = editingTextBox.Text;
}
the "e" also contains information about Row and Column of the Cell. Thus you will know which editor the cell is using. In this case, i assume that it is a textbox.
Hope it help.

Start and Back Button pressed in rapid succession WP7

I asked this question in a similar post but there have been significant updates since then, but still no results so I will try to re-ask the question with the updated information.
Basically I have a pivot view with 4 pivot items. If I create the scenario where I hit the windows key then rapidly press the back key my application will reopen without reconstructing (this is the expected outcome). The functionality of the application is there. I can press application bar buttons etc.
What doesn't work is the pivot items are frozen. If I was on Pivot item A and I press the start and back button quickly I come back to Pivot Item A. If I try to switch Pivot Items, the screen does not update, its "frozen" on Pivot Item A BUT the functionality of Pivot Item B is there. (I know this because the application bar Icons for Pivot Item B are now showing).
I have read many articles on proper tombstoning scenarios and how to approach this problem. My data IS being tombstoned correctly, and upon reactivation the tombstoned data works. No objects are null so I don't have any exceptions being thrown at me.
I check to see if I need to reload the Main ViewModel (I don't need to in this case so the UI elements being created initially are not being re created).
What does fix the problem however is if the application is reconstructed. Lets say I go to the marketplace from my app, let it finish loading and press back, My application will be refreshed and working fine since it properly deactivated and reconstructed istelf. I don't rely on constructors doing all the work so I am not missing any key elements not being set when they aren't fired in the windows/back button scenario.
Does anyone have any idea why my screen would not be updating?
constructor/loaded event/on navigated to event
public MainPage()
{
InitializeComponent();
this.Loaded += new RoutedEventHandler(MainPage_Loaded);
}
private void MainPage_Loaded(object sender, RoutedEventArgs e)
{
if (App.firstTimeLoading == true)
{
App.firstTimeLoading = false;
}
BuildApplicationBar();
}
protected override void OnNavigatedTo(NavigationEventArgs e)
{
this.DataContext = App.ViewModel;
App.viewIdentifier = StringResource.MainPageView;
if (!App.ViewModel.IsDataLoaded)
{
App.ViewModel.LoadData();
String bookTitle;
App.Parser.appBookInfoDict.TryGetValue(CPlayerInventoryKeys.kInventoryKeyTitleShortTitle, out bookTitle);
PivotBackground.Title = bookTitle.ToUpper();
CreatePivotItems();
}
if (App.playerController.chapterPlayer.Source == null)
App.restoreStateClass.RestoreState();
//applies the proper background image
if (App.isDarkTheme)
{
BitmapImage bitmapImage = new BitmapImage(new Uri(StringResource.PanoramaBlackImage, UriKind.Relative));
BackgroundImage.ImageSource = bitmapImage;
BackgroundImage.Opacity = .85;
}
else
{
BitmapImage bitmapImage = new BitmapImage(new Uri(StringResource.PanoramaWhiteImage, UriKind.Relative));
BackgroundImage.ImageSource = bitmapImage;
BackgroundImage.Opacity = .5;
}
if (App.firstTimeLoading == false && PivotBackground.SelectedItem != SuggestedPivotItem)
BuildApplicationBar();
else if (PivotBackground.SelectedItem == SuggestedPivotItem)
{
BuildMarketPlaceApplicationBar();
}
base.OnNavigatedTo(e);
}
I found the answer. Since I had a media element open (play/paused) and I was implementing the "non tombstoned" method of hitting windows key and back button very quickly, the media element source was corrupt. Even though I reset this source, apparently it can be ignored and not function properly. All I had to do was add a line of code to the Application Deactivated handler.
private void Application_Deactivated(object sender, DeactivatedEventArgs e)
{
App.MainAudioPlayer.Source = null; //(only showing line added)
}
The behavior you are describing seems to be solely related to the way you are manipulating data internally and constructing your layout. I tested this both in the emulator and on a couple of physical devices, both producing normal output (even when bound to a view model).
Try creating a new Pivot-based application (without all your data - just using the default template) and see if the problem persists. Also worth mentioning - are you testing on a device or in the emulator?
Are you using transitions from the toolkit?
Are they defined in XAML?
If so that could be the issue. There's a bug which is fixed in the next version.
The solution for now is to remove the transitions or define them in code.

Navigation from user control in win 7 phone

I am not able to call the navigation service from the user control.
even when I create one event handler on the main page to call the navigation service that is laos not working.
Can you please help me?
I think I see the problem, but like Austin indicated, there isn't much to go on in your initial description. It sounds like you are trying to access the NavgationService (which is a PhoneApplicationPage property) from within a UserControl that you are placing on that page.
As with many things in these APIs, you have a couple of options. First, you can access the PhoneApplicationFrame (which contains your pages and manages navigation) and use it for navigation:
var frame = App.Current.RootVisual as PhoneApplicationFrame;
frame.Navigate(new Uri("/TargetPage.xaml", UriKind.Relative));
Alternatively, you can walk the control's Visual Tree using the VisualTreeHelper until you get to the containing page:
var page = GetParentOfType<PhoneApplicationPage>(this); // this is your user control
private static T GetParentOfType<T>(DependencyObject item) where T : DependencyObject
{
if (item == null) throw new ArgumentNullException("item");
T result;
var parent = VisualTreeHelper.GetParent(item);
if (parent == null) return null;
else if (parent.GetType().IsSubclassOf(typeof(T))
{
result = (T)parent;
}
else result = GetParameterOfType<T>(parent);
return result;
}
As you see, the VisualTree approach involves more code, but gets you the containing page object, where you have more access to things like NavigationContext, etc.
Hope that was your question (and your answer.)

How to get activation events in composite children in Composite Application Guidance (PRISM)

I'm using the CAG and I've got some issues using the TabControl region. I worked out that by marking the view (and NOT the PresentationModel) as IActiveAware I can get an event when the view is activated/deactivated. That works well when the composite is simple and the TabItem is the view.
However, in my case I've got a composite inside the TabItem. It can listen to activation events but I'd like to propagate these events to its children so that they can react to them. Is there a way of doing that? I had a look at the RegionContext but it doesn't seem to work in my case (or maybe I'm doing it wrong).
Could it be that I'm missing out on something and an attached dependency or something else would solve my issue?
I decided to use the RegionContext to propagate the IsActive state within the region.
Set it up as:
Regions:RegionManager.RegionContext="{Binding Path=IsActive, Mode=TwoWay}"
on my tab view (which is IActiveAware). Then in the child view I can listen to changes:
RegionContext.GetObservableContext((DependencyObject)View).PropertyChanged += new PropertyChangedEventHandler(VehiclesPresentationModel_PropertyChanged);
private void VehiclesPresentationModel_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
if (e.PropertyName == "Value")
{
IsActive = (bool)RegionContext.GetObservableContext((DependencyObject)View).Value;
}
}
The remaining issue was that the reverse would work. Setting IsActive on the tab view doesn't active the tab :(
I added a custom behavior and now it works. The custom bahavior is like:
public class RegionReverseActiveAwareBehavior : RegionBehavior
{
public const string BehaviorKey = "RegionReverseActiveAwareBehavior";
protected override void OnAttach()
{
Region.Views.CollectionChanged += new NotifyCollectionChangedEventHandler(Views_CollectionChanged);
}
private void Views_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
{
if (e.Action == NotifyCollectionChangedAction.Add)
{
foreach (var item in e.NewItems)
{
IActiveAware activeAwareItem = item as IActiveAware;
if (activeAwareItem != null)
{
activeAwareItem.IsActiveChanged += new EventHandler(activeAwareItem_IsActiveChanged);
}
}
}
if (e.Action == NotifyCollectionChangedAction.Remove)
{
foreach (var item in e.OldItems)
{
IActiveAware activeAwareItem = item as IActiveAware;
if (activeAwareItem != null)
{
activeAwareItem.IsActiveChanged -= new EventHandler(activeAwareItem_IsActiveChanged);
}
}
}
}
private void activeAwareItem_IsActiveChanged(object sender, EventArgs e)
{
IActiveAware activeAware = sender as IActiveAware;
if (activeAware != null &&
activeAware.IsActive)
{
Region.Activate(activeAware);
}
}
}
And then I set it up on the TabControl with:
RegionManager.GetObservableRegion(tabRegion).PropertyChanged +=
(sender, args) =>
{
if (args.PropertyName == "Value")
{
IRegion region = RegionManager.GetObservableRegion(tabRegion).Value;
region.Behaviors.Add(RegionReverseActiveAwareBehavior.BehaviorKey, new RegionReverseActiveAwareBehavior());
}
};
Hope that solves someone else's issue. Or maybe there's an easier way that I'm missing.
Have you looked at Prism EventAggregator? This can be implemented as some sort of MessageBus or Mediator...
You can Puplish events and everyone who needs be interested can subscribe to it...
If you look in the prism samples you find an implementation or something in the docs...
The Prism EventAggregator (EA) object allows you to publish events and subscribe to them through the EA object. This can be used with a single publisher and 0, 1 or many subscribers. I generally use the EA when I need to communicate between different parts of an application that are not tied together. For example, a menu item in the shell of a Prism application may need to invoke another view in a different module. The EA allows you to do this via pub/sub. However if a screen needs to make something happen on its own self, this ifs often better suited for the Command object.

Same module multiple times as TabItems

Here's my scenario:
Shell with 1 TabControl and 1 region called MenuRegion
MenuRegion contains Buttons for each of the available modules (applications).
I want to achieve the following using Prism (Composite Application Library for WPF): When one of the buttons is clicked, I need to add a new TabItem to the TabControl, and load and individual instance of the corresponding module (application) inside this TabItem.
One module may appear several times in the TabControl.
I really appreciate your answer. But I don't believe you're using Prism (http://www.codeplex.com/CompositeWPF) are you? My question was more related to Prism, and I've edited it to be more clear now.
In Prism you dynamically load modules' views into regions. I am not sure how to do that in my scenario because the regions are to be set dynamically. How would I name them?
Thanks!
I'm new to this PRISM world (1 week experience :)) ) and had the same requirement!
First of all you have to get the Regionextensions from here.
The solution to my (may be your) problem is as follows:
have 2 regions (menu and tabcontrol - for mdi like behaviour)
tabitem header has to be prepared with a button for closing (which is bound to a command for closing this tabitem-actually hiding this tabitem)
send event from menu item to the module which should load the view (I've instantiated the modules on demand). In the module's initialize method subscribe to the event sent by the menu item. In the event handling method you simply re-show the tabitem
If this is to abstract to you I can send you a skeleton application I've developed to play around.
We do something similar, though we have the tab items already created (with no content) and show/hide as appropriate. When the tab item is selected, then we load the tab content.
private void TabControl_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
if (e.OriginalSource != sender) return;
TabControl tabControl = (TabControl)sender;
TabItem tabItem = (TabItem)tabControl.SelectedItem;
if (!tabItem.HasContent)
AddTabContent(tabItem); // This will cause a refresh once the content is loaded.
else
Refresh(tabItem);
}
private void AddTabContent(TabItem tabItem)
{
IOptimusPage page = tabItem.Tag as IOptimusPage;
//This allows lazy loading of controls
if (page != null)
{
if (!tabItem.HasContent)
{
CustomerEngagementUserControl control = page.GetControl(DataContext as CustomerEngagementUIObject, Services);
tabItem.Content = control;
}
}
}
The tab item content is specified in the tab item tag, using pages which are responsible for creating the content.
<TabItem
Header="Personal Background"
Style="{StaticResource FirstBreadcrumbTabItem}"
x:Name="PersonalBackgroundTab">
<TabItem.Tag>
<Pages:FfnaPersonalBackgroundPage />
</TabItem.Tag>
</TabItem>
The page creates the control.
class FfnaPersonalBackgroundPage : IOptimusPage
{
#region IOptimusPage Members
public CustomerEngagementUserControl GetControl(CustomerEngagementUIObject dataContext, CustomerEngagementServices services)
{
CustomerEngagementUserControl control = new FfnaPersonalBackgroundControl();
control.DataContext = dataContext;
control.Services = services;
return control;
}
#endregion
}
You could use a similar technique to create your tab items on the fly.
I know it's quite late a response but I am doing something similar, although haven't achieved the full solution yet.
This code happens on the click event of a button which I am handling in the presenter. The module is defined in the config file.
ModuleInfo moduleInfoObject = this.moduleEnumerator.GetModule("ModuleA");
Assembly assembly = this.LoadAssembly(moduleInfoObject);
Type type = assembly.GetType(moduleInfoObject.ModuleType);
IModule aModule = this.CreateModule(type);
aModule.Initialize();
// - - - -Helper Methods - - - -
// - - - LoadAssembly - - -
private Assembly LoadAssembly(ModuleInfo moduleInfo)
{
string assemblyFile = moduleInfo.AssemblyFile;
assemblyFile = this.GetModulePath(assemblyFile);
FileInfo file = new FileInfo(assemblyFile);
Assembly assembly;
try
{
assembly = Assembly.LoadFrom(file.FullName);
}
catch (Exception ex)
{
throw new ModuleLoadException(null, assemblyFile, ex.Message, ex);
}
return assembly;
} // LoadAssembly(moduleInfo)
// - - - CreateModule - - -
private IModule CreateModule(Type type)
{
return (IModule)containerFacade.Resolve(type);
} // CreateModule(type)
// - - - GetModulePath - - -
private string GetModulePath(string assemblyFile)
{
if (String.IsNullOrEmpty(assemblyFile))
{
throw new ArgumentNullException("assemblyFile");
} // if
if (Path.IsPathRooted(assemblyFile) == false)
{
assemblyFile = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, assemblyFile);
} // if
return assemblyFile;
} // GetModulePath(assemblyFile)

Resources