I am working on a Windows 8 Metro Newsreader-App (with C# and XAML). I show the feed-items on a Grouped Items Page (template). A click forwards the user to a detail-page, which I implemented as a Split Page. Therefore, I have an Image-Gallery where the user can navigate from this DetailPage (and back). This works fine. On the ItemDetailPage I have to assign the Data in the LoadState function. The template offers me the following solution:
protected override void LoadState(Object navigationParameter, Dictionary<String, Object> pageState)
{
// TODO: Assign a bindable group to this.DefaultViewModel["Group"]
// TODO: Assign a collection of bindable items to this.DefaultViewModel["Items"]
if (pageState == null)
{
// When this is a new page, select the first item automatically unless logical page
// navigation is being used (see the logical page navigation #region below.)
if (!this.UsingLogicalPageNavigation() && this.itemsViewSource.View != null)
{
this.itemsViewSource.View.MoveCurrentToFirst();
}
}
else
{
// Restore the previously saved state associated with this page
if (pageState.ContainsKey("SelectedItem") && this.itemsViewSource.View != null)
{
// TODO: Invoke this.itemsViewSource.View.MoveCurrentTo() with the selected
// item as specified by the value of pageState["SelectedItem"]
}
}
}
What I did was the following:
protected override void LoadState(Object navigationParameter, Dictionary<String, Object> pageState)
{
if (pageState == null)
{
// When this is a new page, select the first item automatically unless logical page
// navigation is being used (see the logical page navigation #region below.)
if (!this.UsingLogicalPageNavigation() && this.itemsViewSource.View != null)
{
this.itemsViewSource.View.MoveCurrentToFirst();
}
}
else
{
// Restore the previously saved state associated with this page
if (pageState.ContainsKey("SelectedItem") && this.itemsViewSource.View != null)
{
this.itemsViewSource.View.MoveCurrentTo(pageState["SelectedItem"]);
}
}
var item = ArticleDataSource.GetItem((int)navigationParameter);
if (item != null)
{
this.DefaultViewModel["Group"] = item.Group;
this.DefaultViewModel["Items"] = item.Group.Items;
if (this.itemsViewSource.View != null) this.itemsViewSource.View.MoveCurrentTo(item); // remove?
// Register this page as a share source.
this.dataTransferManager = DataTransferManager.GetForCurrentView();
this.dataTransferManager.DataRequested += new TypedEventHandler<DataTransferManager, DataRequestedEventArgs>(this.OnDataRequested);
}
}
If I navigate from the OverviewPage to the DetailsPage the selected item (A) is shown.
I select an other item (from the list) and the correct details (B) are shown.
If I navigate from the DetailsPage to the GalleryPage the images of the correct item (B) are shown.
If I now navigate back (to the DetailsPage) not the last selected item (B) but the item I selected (A) to enter DetailsPage is shown.
I am aware of the fact that I changed the order (proposed by the template) and I added if (this.itemsViewSource.View != null) this.itemsViewSource.View.MoveCurrentTo(item); that I'd probably better remove.
I think that the problem (described in step 4) is, that this.itemsViewSource.View is null and therefore (logically) this.itemsViewSource.View.MoveCurrentTo(pageState["SelectedItem"]) doesn't get executed. Unfortunately, I was unable to find out why or if this is the bug.
Any help or link to a tutorial (which could solve my problem) are really much appreciated! thanks.
The point is to override the navigationParameter as needed by the previous page state. The item is then loaded and selected. Try using
// Override the navigationParameter if a page state is set:
if (pageState != null && pageState.ContainsKey("SelectedItem"))
{
navigationParameter = pageState["SelectedItem"];
}
var item = ArticleDataSource.GetItem((int)navigationParameter);
if (item != null)
{
DefaultViewModel["Group"] = item.Group;
DefaultViewModel["Items"] = item.Group.Items;
if (itemsViewSource.View != null)
{
itemsViewSource.View.MoveCurrentTo(item);
}
else
{
// A serious error happened here..
}
}
else
{
// Oooops, an item disappeared..
}
Related
There's a toolstripmenuitem in my Windows form Application.
I need to access every sub menu items and check whether there is a specific menu item name is available and if that item found, I want to disable it.
Eg:
Report
|__Stock
| |__Stock Balance
| |__Stock Reorder
|__Sales
|__Summary
My code is like this. According to my code, I can access sub menu(Stock) and disable it. But I'm unable to access child items(Stock Balance) inside sub menu.
String specificMenuItemName = "Stock Balance";
foreach (ToolStripMenuItem menuItem in MainMenuStrip.Items)
{
if (menuItem != null)
{
if (menuItem.HasDropDownItems)
{
foreach (ToolStripItem subMenuItem in menuItem.DropDownItems)
{
if (subMenuItem is ToolStripSeparator)
{ }
else
{
if (specificMenuItemName == subMenuItem.Text)
{
subMenuItem.Enabled = false;
}
}
}
}
}
}
How do I access to Stock Balance and disable it?
What about a recursive function that walks down every item that has drop-down items until it finds the one with the specified name? something like this (quick-and-dirty, skipped checking for separators and stuff like that...):
private static void DisableItem(ToolStripDropDownItem menu, bool enable, string text)
{
if (!menu.HasDropDownItems)
if (Equals(menu.Text, text))
menu.Enabled = enable;
else
return;
foreach(var subItem in menu.DropDownItems)
{
var item = subItem as ToolStripDropDownItem;
if (item == null) continue;
if (item.HasDropDownItems)
DisableItem(item, enable, text);
if (Equals(item.Text, text))
item.Enabled = enable;
}
}
I am working on C# add-ins in Enterprise Architect to give a restriction to user so that only a particular child element can be added to a specific parent element.
For example if child element A must be dropped on parent element B it is deleted if child element A is dragged and dropped on parent element C. I am using EA_OnPostNewElement method and a delete method for the same and it works fine.
My doubt is, after the user has dropped the child element on the specific parent, after some time he can drag the child element outside the parent element and add it as a child to any other element in the diagram.
Is there a way to add a restriction here by observing the changes made by user on Enterprise architect GUI and bring back the child element to original parent location. Kindly help.
You need to use both EA_OnContextItemChanged and EA_OnNotifyContextItemModified so you can achieve it .
declare a dictonry
public Dictionary<int, int> lstChildElements = new Dictionary<int, int>();
and here is the sample code
public void EA_OnContextItemChanged(EA.Repository Repository, string GUID, EA.ObjectType ot)
{
EA.Element addedElement = Repository.GetElementByGuid(GUID);
if (addedElement == null)
return;
int identifiedParentID = 0;
bool isAvailable = lstChildElements.TryGetValue(addedElement.ElementID, out identifiedParentID);
if (!isAvailable)
{
if (addedElement.Stereotype == "Child" && addedElement.ParentID != 0)
{
EA.Element parentElemnt = Session.Repository.GetElementByID(addedElement.ParentID);
if (parentElemnt != null)
if (parentElemnt.Stereotype == "anyCustomDefined")
lstChildElements.Add(addedElement.ElementID, addedElement.ParentID);
}
}
}
public void EA_OnNotifyContextItemModified(EA.Repository Repository, string GUID, EA.ObjectType ot)
{
EA.Element addedElement = Repository.GetElementByGuid(GUID);
if (addedElement == null)
return;
int identifiedParentID = 0;
bool isAvailable = lstChildElements.TryGetValue(addedElement.ElementID, out identifiedParentID);
if (isAvailable)
{
if (addedElement.Stereotype == "Child" && addedElement.ParentID != 0)
{
EA.Element parentElemnt = Repository.GetElementByID(addedElement.ParentID);
if (parentElemnt != null)
if (parentElemnt.Stereotype != "anyCustomDefined")
{
addedElement.ParentID = identifiedParentID != 0 ? identifiedParentID : addedElement.ParentID;
addedElement.Update();
lstChildElements[addedElement.ElementID] = addedElement.ParentID;
}
}
else if (addedElement.Stereotype == "Child" && addedElement.ParentID == 0)
{
addedElement.ParentID = identifiedParentID;
addedElement.Update();
}
}
}
Hope it helps..!
and for updating in diagram need to reload it.
EA.Diagram activeDiagram = Session.Repository.GetCurrentDiagram();
if (activeDiagram != null)
Session.Repository.ReloadDiagram(activeDiagram.DiagramID);
or
Repository.RefreshOpenDiagrams();
Both the codes can be used for reloading the diagram.
You can use the context events to keep track of the selected element and it's owner.
I'm not sure if the event EA_OnNotifyContextItemModified is being fired when you change the owner of an element.
But even it that is not the case you can verify if it still has the same owner after a new element has been selected.
I'm trying to come up with a System.Windows.Interactivity.Behaviour that, when applied to a WPF DataGrid, adds a context menu (or items to an existing context menu) that allow users to show or hide columns.
I came up with a solution that almost works very well.
Everything works just as expected - until you hide and then re-show a column. Once becoming visible again, the contextmenu just seems to disappear, right-clicking on the column doesn't do anything anymore.
Code is below, verbalyl what I'm doing:
On attaching the behavior, I start listening to the DataGrid "Loaded" event
In the Loaded event, I find all DataGridColumnHeader descendants of the DataGrid
For each of these, I generate an individual context menu and attach it to the DataGridColumnHeader
For each context menu, I generate one menu item per column, and assign a command to it that, upon execution, sets the DataGridColumn's visibility to Visible or Hidden
I've stripped down the code to a minimum example for the simplest case: To test this, simply apply that behavior to a DataGrid that doesn't have a ContextMenu assigned currently.
public class DgColumnBehavior : Behavior<DataGrid>
{
protected ICommand ToggleColumnVisibilityCmd;
protected DataGrid _AssociatedObject;
protected override void OnAttached()
{
this.ToggleColumnVisibilityCmd = new DelegateCommand<DataGridColumn>(ToggleColumnVisibilityCmdExecute);
this._AssociatedObject = (DataGrid)this.AssociatedObject;
Observable.FromEventPattern(this._AssociatedObject, "Loaded")
.Take(1)
.Subscribe(x => _AssociatedObject_Loaded());
base.OnAttached();
}
void _AssociatedObject_Loaded()
{
var columnHeaders = this._AssociatedObject.SafeFindDescendants<DataGridColumnHeader>(); // see second code piece for the SafeFindDescendants extension method
foreach (var columnHeader in columnHeaders)
{
EnsureSeparateContextMenuFor(columnHeader);
if (columnHeader.ContextMenu.ItemsSource != null)
{
// ContextMenu has an ItemsSource, so need to add items to that -
// ommitted though as irrelevant for example
}
else
{
// No ItemsSource assigned to the Menu, so we can just add directly
foreach (var item in CreateMenuItemsFor(columnHeader))
columnHeader.ContextMenu.Items.Add(item);
}
}
}
/// Ensures that the columnHeader ...
/// A) has a ContextMenu, and
/// B) that is has an individual context menu, i.e. one that isn't shared with any other DataGridColumnHeaders.
///
/// I'm doing that as in practice, I'm adding some further items that are specific to each column, so I can't have a shared context menu
private void EnsureSeparateContextMenuFor(DataGridColumnHeader columnHeader)
{
if (columnHeader.ContextMenu == null)
{
columnHeader.ContextMenu = new ContextMenu();
}
else
{
// clone the existing menu
// ommitted as irrelevant for example
}
}
/// Creates one menu item for each column of the underlying DataGrid to toggle that column's visibility
private IEnumerable<FrameworkElement> CreateMenuItemsFor(DataGridColumnHeader columnHeader)
{
foreach (var column in _AssociatedObject.Columns)
{
var item = new MenuItem();
item.Header = String.Format("Toggle visibility for {0}", column.Header);
item.Command = ToggleColumnVisibilityCmd;
item.CommandParameter = column;
yield return item;
}
}
// Gets executed when the user clicks on one of the ContextMenu items
protected void ToggleColumnVisibilityCmdExecute(DataGridColumn column)
{
bool isVisible = (column.Visibility == Visibility.Visible);
Visibility newVisibility = (isVisible) ? Visibility.Hidden : Visibility.Visible;
column.Visibility = newVisibility;
}
}
The SafeFindDescendants extension method is heavily based on the one from here: DataGridColumnHeader ContextMenu programmatically
public static class Visual_ExtensionMethods
{
public static IEnumerable<T> SafeFindDescendants<T>(this Visual #this, Predicate<T> predicate = null) where T : Visual
{
if (#this != null)
{
int childrenCount = VisualTreeHelper.GetChildrenCount(#this);
for (int i = 0; i < childrenCount; i++)
{
var currentChild = VisualTreeHelper.GetChild(#this, i);
var typedChild = currentChild as T;
if (typedChild == null)
{
var result = ((Visual)currentChild).SafeFindDescendants<T>(predicate);
foreach (var r in result)
yield return r;
}
else
{
if (predicate == null || predicate(typedChild))
{
yield return typedChild;
}
}
}
}
}
}
I can't figure out what's going on. Why does the context menu seem to be removed after hiding/re-showing a column?!
Appreciate any ideas! Thanks.
I've come up with a quick and dirty Fix. It works, but it isn't pretty. Maybe someone can think of a better solution.
Essentially, each time the visibility of a DataGridColumn item changes to hidden/collapsed, I retrieve its DataGridColumnHeader and store the associated context menu in a cache.
And each time the visibility changes back to visible, I'm listening to the next DataGrid LayoutUpdated event (to ensure the visual tree has been built), retrieve the DataGridColumnHeader again - which will incoveniently be a different instance than the original one - and set its context menu to the cached one.
protected IDictionary<DataGridColumn, ContextMenu> _CachedContextMenues = new Dictionary<DataGridColumn, ContextMenu>();
protected void ToggleColumnVisibilityCmdExecute(DataGridColumn column)
{
bool isVisible = (column.Visibility == Visibility.Visible);
Visibility newVisibility = (isVisible) ? Visibility.Hidden : Visibility.Visible;
if(newVisibility != Visibility.Visible)
{
// We're hiding the column, so we'll cache its context menu so for re-use once the column
// becomes visible again
var contextMenu = _AssociatedObject.SafeFindDescendants<DataGridColumnHeader>(z => z.Column == column).Single().ContextMenu;
_CachedContextMenues.Add(column, contextMenu);
}
if(newVisibility == Visibility.Visible)
{
// The column just turned visible again, so we set its context menu to the
// previously cached one
Observable
.FromEventPattern(_AssociatedObject, "LayoutUpdated")
.Take(1)
.Select(x => _AssociatedObject.SafeFindDescendants<DataGridColumnHeader>(z => z.Column == column).Single())
.Subscribe(x =>
{
var c = x.Column;
var cachedMenu = _CachedContextMenues[c];
_CachedContextMenues.Remove(c);
x.ContextMenu = cachedMenu;
});
}
column.Visibility = newVisibility;
}
Have you already found a better solution?
I have a new DataGrid class, so "this" is the actual Instance of a DataGrid!
This is my solution (I'm also listen on the LayoutUpdated event):
this.LayoutUpdated += (sender, args) =>
{
foreach (DataGridColumnHeader columnHeader in GetVisualChildCollection<DataGridColumnHeader>(this))
{
if(columnHeader.ContextMenu == null)
ContextMenuService.SetContextMenu(columnHeader, _ContextMenu);
}
};
public static List<T> GetVisualChildCollection<T>(object parent) where T : Visual
{
List<T> visualCollection = new List<T>();
GetVisualChildCollection(parent as DependencyObject, visualCollection);
return visualCollection;
}
private static void GetVisualChildCollection<T>(DependencyObject parent, List<T> visualCollection) where T : Visual
{
int count = VisualTreeHelper.GetChildrenCount(parent);
for (int i = 0; i < count; i++)
{
DependencyObject child = VisualTreeHelper.GetChild(parent, i);
if (child is T)
{
visualCollection.Add(child as T);
}
else if (child != null)
{
GetVisualChildCollection(child, visualCollection);
}
}
}
I am using a PropertyGrid to display the content of an object to the user.
This PropertyGrid is synchronized with an Excel sheet, assuming a cell value match a property value.
As the user selects a property in the PropertyGrid, the application highlights the corresponding cell in the Excel sheet which is opened beside. I can do this using the SelectedGridItemChanged event.
Now, I want to have a property selected in my PropertyGrid when the user selects a cell in the Excel sheet.
void myWorkbook_SheetSelectionChangeEvent(NetOffice.COMObject Sh, Excel.Range Target)
{
if (eventMask > 0)
return;
try
{
eventMask++;
this.Invoke(new Action(() =>
{
propertyGrid1.SelectedGridItem = ... // ?
}
}
finally
{
eventMask--;
}
}
I noticed that the SelectedGridItem can be written to.
Unfortunately I do not find a way to access the GridItems collection of my PropertyGrid so I can look for the right GridItem and select it.
How can I do this?
You can get all the GridItems from the root. I am using the below code to retrieve all the griditems within a property grid
private GridItem Root
{
get
{
GridItem aRoot = myPropertyGrid.SelectedGridItem;
do
{
aRoot = aRoot.Parent ?? aRoot;
} while (aRoot.Parent != null);
return aRoot;
}
}
and pass the root to the below method
private IList<GridItem> GetAllChildGridItems(GridItem theParent)
{
List<GridItem> aGridItems = new List<GridItem>();
foreach (GridItem aItem in theParent.GridItems)
{
aGridItems.Add(aItem);
if (aItem.GridItems.Count > 0)
{
aGridItems.AddRange(GetAllChildGridItems(aItem));
}
}
return aGridItems;
}
I came up against this quite a bit a project so I wrote an extension method for it:
if (!SetupManagerSettings.BootStrapperLocation.IsFile()) // Just another extension method to check if its a file
{
settingsToolStripMenuItem.Checked = true; // Event handler OnChecked ensures the settings panel is unhidden
settingsPropertyGrid.ActivateControl();
settingsPropertyGrid.SelectPropertyGridItemByName("BootStrapperLocation"); // Here is the extension method
return false;
}
Here is the extension method with a private, supporting method for traversing the hierarchy of objects (if this applies to your object model):
public static bool SelectPropertyGridItemByName(this PropertyGrid propertyGrid, string propertyName)
{
MethodInfo getPropEntriesMethod = propertyGrid.GetType().GetMethod("GetPropEntries", BindingFlags.NonPublic | BindingFlags.Instance);
Debug.Assert(getPropEntriesMethod != null, #"GetPropEntries by reflection is still valid in .NET 4.6.1 ");
GridItemCollection gridItemCollection = (GridItemCollection)getPropEntriesMethod.Invoke(propertyGrid, null);
GridItem gridItem = TraverseGridItems(gridItemCollection, propertyName);
if (gridItem == null)
{
return false;
}
propertyGrid.SelectedGridItem = gridItem;
return true;
}
private static GridItem TraverseGridItems(IEnumerable parentGridItemCollection, string propertyName)
{
foreach (GridItem gridItem in parentGridItemCollection)
{
if (gridItem.Label != null && gridItem.Label.Equals(propertyName, StringComparison.OrdinalIgnoreCase))
{
return gridItem;
}
if (gridItem.GridItems == null)
{
continue;
}
GridItem childGridItem = TraverseGridItems(gridItem.GridItems, propertyName);
if (childGridItem != null)
{
return childGridItem;
}
}
return null;
}
I looked at the above options and did not like them that much, I altered it a bit found that this works well for me
bool TryFindGridItem(PropertyGrid grid, string propertyName, out GridItem discover)
{
if (grid is null)
{
throw new ArgumentNullException(nameof(grid));
}
if (string.IsNullOrEmpty(propertyName))
{
throw new ArgumentException("You need to provide a property name", nameof(propertyName));
}
discover = null;
var root = pgTrainResult.SelectedGridItem;
while (root.Parent != null)
root = root.Parent;
foreach (GridItem item in root.GridItems)
{
//let's not find the category labels
if (item.GridItemType!=GridItemType.Category)
{
if (match(item, propertyName))
{
discover= item;
return true;
}
}
//loop over sub items in case the property is a group
foreach (GridItem child in item.GridItems)
{
if (match(child, propertyName))
{
discover= child;
return true;
}
}
//match based on the property name or the DisplayName if set by the user
static bool match(GridItem item, string name)
=> item.PropertyDescriptor.Name.Equals(name, StringComparison.Ordinal) || item.Label.Equals(name, StringComparison.Ordinal);
}
return false;
}
it uses a local method named match, if you're version of C# does not allow it then just put it external to the method and perhaps give it a better name.
Match looks at the DisplayName as well as the property name and returns true if either is a match, this might not be what you would like so then update that.
In my usecase I need to select the property based on a value the user can select so call the above method like this:
if (TryFindGridItem(pgMain, propertyName, out GridItem gridItem))
{
gridItem.Select();
}
It could theoretically never not find it unless the user selects a string that is not proper this way the if can be updated using an else .. I rather keep it safe then have a null-pointer exception if I can't find the name specified.
Also I am not going into sub classes and lists of classes as my use case doesn't need that, if yours does than perhaps make recursive calls in the GridItem.
Small note, replace GridItem with var and the code brakes as at compile time the IDE has no clue what a GridItemCollection returns…
I have a headered items control bound to an observable collection. The items in the collection are associated with a data template. The data template is pretty simple in that it only contains two text boxes. The part I am having difficulty with is setting focus to the text boxes programatically.
When the items control is first displayed it always has one item in it. It may or may not have a value that is bound to the first text box. If it has a value, the focus needs to be set to the second text box. I've done this using the following:
protected override void OnActivated(EventArgs e)
{
base.OnActivated(e);
if (_activated) return;
_activated = true;
var dc = DataContext as MyViewModel;
if (null != dc)
{
var cp = theItemsControl.ItemContainerGenerator.ContainerFromIndex(0) as ContentPresenter;
var tb = FindVisualChildren<TextBox>(cp);
if (tb != null)
{
if (!String.IsNullOrEmpty(dc.TheCollection.First().FirstValue))
{
var t = tb.FirstOrDefault(box => box.Name == "SecondTextBox");
if (null != t) t.Focus();
}
else
{
var t = tb.FirstOrDefault(box => box.Name == "FirstTextBox");
if (null != t) t.Focus();
}
}
}
}
This seems to work fine. However, now, when a new item is added to the collection I need to set focus to the FirstTextBox. I've tried setting focus in the datatemplate using the FocusManager, but that prohibits the above code from working correctly. I've also tried the following in the view. The method gets called from the ViewModel after the new item has been added to the ObservableCollection. The code runs, but it fails to find any textboxes (ie. tb == null). Have the controls been created at this point?
public void SetFocusToNewItem(int index)
{
var dc = DataContext as MyViewModel;
if (null != dc)
{
var cp = theItemsControl.ItemContainerGenerator.ContainerFromIndex(index) as ContentPresenter;
var tb = FindVisualChildren<TextBox>(cp);
if (tb != null)
{
var t = tb.FirstOrDefault(box => box.Name == "FirstTextBox");
if (null != t) t.Focus();
}
}
}
Are there other solutions to set focus after the item has been added?
Sometimes the following trick works for me:
// Replace such call:
SetFocusToNewItem(newIndex);
// By this:
Dispatcher.BeginInvoke((Action) (() =>
{
SetFocusToNewItem(newIndex);
}));
The problem is when you add an item in collection UI elements is not created, so you can't get it. This code above delays your function executing and you method should be executed after UI is created.
PS: You can move Dispatcher call inside of your method.
EDIT:
This may help too (works for me):
var cp = theItemsControl.ItemContainerGenerator.ContainerFromIndex(index) as ContentPresenter;
cp.Loaded += (s, e2) =>
{
var tb = FindVisualChildren<TextBox>(e2.Source as UIElement)
.FirstOrDefault();
if (tb != null)
tb.Focus();
};
The reason is the same: UI element was not created.