Prism, AvalonDock Region Adapter (Handling close DocumentPane event) - wpf

I have a Prism v4 / MEF / WPF solution that loads module views into a DocumentPane in my Avalon Dock. I'm trying to create a handle on the close event (when the 'X' is pressed to close a DocumentPane). I can't figure it out, this is the portion of my RegionAdapter class that creates the DockableContent and also where I attempt to handle the close event (I tried with 3 events):
private void OnViewsCollectionChanged(object sender, NotifyCollectionChangedEventArgs e, IRegion region, DocumentPane regionTarget)
{
if (e.Action == NotifyCollectionChangedAction.Add)
{
foreach (object item in e.NewItems)
{
UIElement view = item as UIElement;
if (view != null)
{
DockableContent newContentPane = new DockableContent()
{
Content = item,
Title = ((ITabViewInfo)view).TabViewTitle
Icon = ((ITabViewInfo)view).TabViewIcon
};
newContentPane.Closed += new EventHandler(newContentPane_Closed);
newContentPane.Manager.DocumentClosed +=new EventHandler(Manager_DocumentClosed);
newContentPane.Manager.DocumentClosing +=new EventHandler<System.ComponentModel.CancelEventArgs>(Manager_DocumentClosing);
regionTarget.Items.Add(newContentPane);
newContentPane.Activate();
}
}
}
else if (e.Action == NotifyCollectionChangedAction.Remove)
{
// this is never hit
Debug.WriteLine("removed");
}
}
These events are never hit (I have a debug.writeline in each one):
newContentPane.Closed += new EventHandler(newContentPane_Closed);
newContentPane.Manager.DocumentClosed +=new EventHandler(Manager_DocumentClosed);
newContentPane.Manager.DocumentClosing +=new EventHandler<System.ComponentModel.CancelEventArgs>(Manager_DocumentClosing);
How can I handle the close event of a DocumentPane?
The code I used for this RegionAdapter is based off of http://blog.raffaeu.com/archive/2010/07/04/wpf-and-prism-tab-region-adapter-part-02.aspx

Found the answer here http://avalondock.codeplex.com/discussions/231982?ProjectName=avalondock
I had to add:
IsCloseable = true
HideOnClose = false

Related

How can I validate only the controls inside a panel?

I have a form with two panels that each have a [Save] button.
How can I validate all of the controls inside each panel separately?
I was hoping that the Panel class would have a Validate() method but it doesn't. It's also not a ContainerControl so it also doesn't have a ValidateChildren method.
What's the best way to accomplish this?
If you set your Form's AutoValidate mode to EnableAllowFocusChange, and presuming you have validating events hooked up to each of the controls inside your panel, something like this:
private void tb_Validating(object sender, CancelEventArgs e)
{
TextBox tb = sender as TextBox;
if (tb != null)
{
if (tb.Text == String.Empty)
{
errorProvider1.SetError(tb, "Textbox cannot be empty");
e.Cancel = true;
}
else
errorProvider1.SetError(tb, "");
}
}
Then on the Click handler for your save button, you can just do this:
private void SaveButton_Click(object sender, EventArgs e)
{
foreach (Control c in panel1.Controls)
c.Focus();
// If you want to summarise the errors
StringBuilder errorSummary = new StringBuilder();
foreach (Control c in panel1.Controls){
String error = errorProvider1.GetError(c);
if (error != String.Empty)
errorSummary.AppendFormat("{0}{1}", errorProvider1.GetError(c), Environment.NewLine);
}
if(errorSummary.Length>0)
MessageBox.Show(errorSummary.ToString());
}
That will cause the validation to fire on each of the controls within the panel.

How to get the Logical Children of a RowDefinition?

I have Extended the RowDefinition as RowDefinitionExtended and In that, when can i get the LogicalChildren belongs to this RowDefinition. I mean in which override can i get the LogicalChildren?
public class RowDefinitionExtended : RowDefinition
{
protected override void OnInitialized(EventArgs e)
{
base.OnInitialized(e);
Loaded += OnRowDefinitionExtendedLoaded;
}
void OnRowDefinitionExtendedLoaded(object sender, RoutedEventArgs e)
{
var parent = GetUIParentCore() as Grid;
if (parent == null) return;
if (parent.Children.Cast<UIElement>().Where(c => Grid.GetRow(c) == parent.RowDefinitions.IndexOf(this)).All(ctrl => ctrl.Visibility != Visibility.Visible))
Height = new GridLength(0);
}
}
What my requirement is, I need to check all the LogicalChildren to its Visibility and Change its Height accordingly.
How could i do this? Any idea?
Update:
Code has been updated, On Load I could do this and it works fine. But my problem is, am changing the controls visibility after load... So is there any notification while changing the Visibility? am looking a event when the layout updated like..
Any event can i use it for?
You can't do that by means of a derived RowDefinition, but this little helper method should do the job (if your intention was to get all child elements in a certain row of a Grid):
public static IEnumerable<UIElement> ChildrenInRow(Grid grid, int row)
{
return grid.Children.Cast<UIElement>().Where(c => Grid.GetRow(c) == row);
}
You have to subscribe to the IsVisibleChanged handler for each element in the row when the row is loaded.
When the visibility changed, you could do whatever you need
public class RowDefinitionExtended : RowDefinition
{
protected override void OnInitialized(EventArgs e)
{
base.OnInitialized(e);
Loaded += OnRowDefinitionExtendedLoaded;
}
void OnRowDefinitionExtendedLoaded(object sender, RoutedEventArgs e)
{
var parent = GetUIParentCore() as Grid;
if (parent == null) return;
//Subscribe to the IsVisibleChanged handler for each element in the row
var ElementInGridRow = parent.Children.Cast<UIElement>().Where(c => Grid.GetRow(c) == parent.RowDefinitions.IndexOf(this));
foreach (var element in ElementInGridRow)
{
element.IsVisibleChanged+=new DependencyPropertyChangedEventHandler(OnChildrenIsVisibleChanged);
}
}
private void OnChildrenIsVisibleChanged(object sender,DependencyPropertyChangedEventArgs e)
{
UIElement element = sender as UIElement;
//Do stuff...
var parent = GetUIParentCore() as Grid;
if (parent.Children.Cast<UIElement>().Where(c => Grid.GetRow(c) == parent.RowDefinitions.IndexOf(this)).All(ctrl => ctrl.Visibility != Visibility.Visible))
Height = new GridLength(0);
}
}

A WPF docking library supporting MVVM

I've already seen some questions like this one, but the docking library I'd like to use must have an important feature, which was not asked: it must support MVVM.
So, among Telerik, DotNetBar, DevZest, and the other libraries around there (excluding AvalonDock, which I have already tested), is there one you actually use with MVVM?
Thanks in advance
Hello Mike try with this:
Easy way: Implement Sofa, An adaptation of AvalonDock for Prism
Using AvalonDock and implementing a custom region adapter like this:
public class ResizingPanelRegionAdapter : RegionAdapterBase<DockingManager>
{
public ResizingPanelRegionAdapter(IRegionBehaviorFactory factory)
: base(factory)
{
}
protected override IRegion CreateRegion()
{
return new AllActiveRegion();
}
protected override void Adapt(IRegion region, DockingManager regionTarget)
{
region.Views.CollectionChanged += delegate(Object sender, NotifyCollectionChangedEventArgs e)
{
OnViewsCollectionChanged(sender, e, region, regionTarget);
};
}
private void OnViewsCollectionChanged(object sender, NotifyCollectionChangedEventArgs e, IRegion region, DockingManager regionTarget)
{
if (e.Action == NotifyCollectionChangedAction.Add)
{
foreach (object item in e.NewItems)
{
UIElement view = item as UIElement;
if (view != null)
{
//Get
ResizingPanel resizingPanel = GetResizingPanel(regionTarget.Content);
resizingPanel.Background = Brushes.White;
DocumentPane document = GetDocumentPane(resizingPanel.Children);
//document.Background = Brushes.White;
DocumentContent newContentPane = new DocumentContent();
newContentPane.Content = item;
var itemView = (item as IViewBase);
if (itemView != null)
newContentPane.Title = itemView.Title;
//When contentPane is closed remove the associated region
newContentPane.Closed += (contentPaneSender, args) =>
{
region.Remove(item);
newContentPane.Content = null;
};
document.Items.Add(newContentPane);
if (!resizingPanel.Children.Contains(document))
resizingPanel.Children.Add(document);
regionTarget.Content = resizingPanel;
newContentPane.Activate();
region.Activate(item);
}
}
}
else if (e.Action == NotifyCollectionChangedAction.Remove)
{
}
}
private DocumentPane GetDocumentPane(UIElementCollection collection)
{
foreach (object item in collection)
{
var documentPanel = item as DocumentPane;
if (documentPanel != null)
return documentPanel;
}
return new DocumentPane();
}
private ResizingPanel GetResizingPanel(object content)
{
var resizingPanel = content as ResizingPanel;
if (resizingPanel != null)
return resizingPanel;
return new ResizingPanel();
}
}
And your in your XAML you could implement it like this:
<avalon:DockingManager prism:RegionManager.RegionName="MainRegion">
</avalon:DockingManager>
How it works?
Simple, first at all you have to keep in mind that Region adapters are responsible for creating a region and associating it with the control. This allows you to use the IRegion interface to manage the UI control contents in a consistent way.
And a DockingManager is the core control in AvalonDock. It arranges contained panes, handles fly out panes and floating windows.
So, following this example you could have implemented a custom region adapter for avalon, I worked with this implementation in a project getting awesome results.
Regards

In Silverlight Datagrid, How to add a new row when focus goes away from Last Row?

I want to add a new row in my Silverlight DataGrid, when user try to go from LastRow to NextRow by Tab/Enter (as it last row, DataGrid loses focus). I can not use RowEditEnded event as it will fire even if i move to a PreviousRow from LastRow.
Can anyone help me achieve this?
If you look at DataGrid source code you can see that it traps key down event (f.i. to realize functionality like go to next row on enter pressed). As solution I propose to implement own grid inherited from DataGrid and add event which raised when user presses enter(or other) button. Own control:
public class MyDataGrid : DataGrid
{
public event EventHandler OnLastRowEnterPressed;
protected override void OnKeyDown(KeyEventArgs e)
{
base.OnKeyDown(e);
if (ItemsSource != null
&& ItemsSource.Cast<object>().Count() - 1 == SelectedIndex
&& e.Key == Key.Enter)
{
RaiseLastRowEnterPressed();
}
}
private void RaiseLastRowEnterPressed()
{
if (OnLastRowEnterPressed != null)
OnLastRowEnterPressed(this, EventArgs.Empty);
}
}
Using:
ObservableCollection<Foo> source = new ObservableCollection<Foo>()
{
new Foo(), new Foo(), new Foo(),
};
myDataGrid.OnLastRowEnterPressed += (s, e) => source.Add(new Foo());
myDataGrid.ItemsSource = source;
well vladimir, there seems to be no simple/direct way to add new row after last row exit. your solution will work but with consequences of event being raised in other key press events also. i have come up with the combination of events to get the solution.
protected override void OnKeyDown(KeyEventArgs e)
{
base.OnKeyDown(e);
addFlag = (e.Key == Key.Tab);
}
protected override void OnLostFocus(RoutedEventArgs e)
{
addFlag = (addFlag && true);
base.OnLostFocus(e);
}
protected override void OnRowEditEnded(DataGridRowEditEndedEventArgs e)
{
base.OnRowEditEnded(e);
addFlag = (addFlag && IsLastRowSelected);
if (addFlag)
AddItem();
addFlag = false;
}
protected override void OnKeyUp(KeyEventArgs e)
{
base.OnKeyUp(e);
addFlag = false;
}
protected override void OnSelectionChanged(SelectionChangedEventArgs e)
{
base.OnSelectionChanged(e);
addFlag = false;
}
private void AddItem()
{
if (RaiseAddEvent!= null)
{
this.Focus();
RaiseAddEvent(this, EventArgs.Empty);
this.UpdateLayout();
this.CurrentColumn = this.Columns[0];
this.BeginEdit();
}
}
You can use Routed events concept where trapping the Enter/Tab key , you can add new row to the data grid control.
I will expose by few steps. So lets start now..
1)Declare your event in constructor of this class.
this.DataGrid1.KeyDown += new KeyEventHandler(DataGrid1_KeyDown);
you also can it in XAML file.
...KeyDown="DataGrid1_KeyDown".....
2) Go to your keydown event & wrie the code.
var focusedElement = FocusManager.GetFocusedElement();
DataGrid detailsDataGrid = sender as DataGrid;
int dataGridrows = detailsDataGrid.ItemsSource.OfType<object>().Count();
if (e.Key == Key.Tab && (Keyboard.Modifiers & ModifierKeys.Shift) == ModifierKeys.Shift)
return;
if (e.Key == Key.Tab)
try
{
detailsDataGrid.SelectedIndex = row.GetIndex();
{
itemMaster.TransactionChilds.Add(transactionChild);
detailsDataGrid.SelectedItem = transactionChild;
}
}
3) Now code line by line..
DataGridRow row = DataGridRow.GetRowContainingElement(focusedElement as FrameworkElement);
DataGridColumn column = DataGridColumn.GetColumnContainingElement(focusedElement as FrameworkElement);
TransactionMaster itemMaster = this.DataFormVoucher.CurrentItem as TransactionMaster;
decimal serialNumber = 0;
if (buttonPress == "Modify")
if (dataGridrows - 1 == detailsDataGrid.SelectedIndex && column.DisplayIndex == 5)
TransactionChild transactionChild = new TransactionChild()"[None]",DateTime.Now.Date,catch (Exception ex)Console.WriteLine(ex.Message);
.DataGridChild.KeyDown += new KeyEventHandler(DataGridChild_KeyDown);
3) Now understand the code line by line
i) first 3 lines are used to take which row of a datagrid is selected.
ii)When new row will add in this case i have used Tab key you can also change this.Another things is if an user predd Tab+Shift then it will go through (default as control focus).
iii) then check is it last row & last column of this grid, if yes then add new row or else.
iv) to add a blank new row just pass your object (EDMX Model Table)

Getting the TreeViewItem on newly created items

There has to be a better way then the following for getting "childItem"
TaskItem task = (sender as Canvas).DataContext as TaskItem;
TaskItem child = Tasks.CreateTask("New task", task);
TreeViewItem item = treeView.ItemContainerGenerator.ContainerFromItem(task) as TreeViewItem;
item.UpdateLayout();
TreeViewItem childItem = null;
foreach (var i in item.GetDescendantContainers())
{
if (i.GetItem() == child)
childItem = i;
}
For some reason item.ItemGenerator.ContainerFromItem(child) does not work (must be due to the item having just been created)
Item container generation is asynchronous, so you cannot assume the container will exist as soon as the item was added. You will need to attach a handler to the ItemContainerGenerator.StatusChanged event so your code will be informed when container generation is complete.
Dr. WPF's blog entry "ItemsControl: 'G' is for Generator" has a good description of the problem and provides an example of using StatusChanged:
private void AddScooby()
{
_scooby = new Character("Scooby Doo");
Characters.Add(_scooby);
CharacterListBox.ItemContainerGenerator.StatusChanged
+= OnStatusChanged;
}
private void OnStatusChanged(object sender, EventArgs e)
{
if (CharacterListBox.ItemContainerGenerator.Status
== GeneratorStatus.ContainersGenerated)
{
CharacterListBox.ItemContainerGenerator.StatusChanged
-= OnStatusChanged;
ListBoxItem lbi = CharacterListBox.ItemContainerGenerator
.ContainerFromItem(_scooby) as ListBoxItem;
if (lbi != null)
{
lbi.IsSelected = true;
}
}
}

Resources