private static List <_ShellObjectPair> GenerateJumpItems(Standard.IObjectArray shellObjects) { List <_ShellObjectPair> list = new List <_ShellObjectPair>(); Guid riid = new Guid("00000000-0000-0000-C000-000000000046"); uint count = shellObjects.GetCount(); for (uint i = 0; i < count; i++) { object at = shellObjects.GetAt(i, ref riid); JumpItem jumpItemForShellObject = null; try { jumpItemForShellObject = GetJumpItemForShellObject(at); } catch (Exception exception) { if ((exception is NullReferenceException) || (exception is SEHException)) { throw; } } _ShellObjectPair pair = new _ShellObjectPair { ShellObject = at, JumpItem = jumpItemForShellObject }; list.Add(pair); } return(list); }
private void _BuildShellLists(out List<JumpItem> successList, out List<_RejectedJumpItemPair> rejectedList, out List<_ShellObjectPair> removedList) { // Declare these outside the try block so we can cleanup native resources in the _ShellObjectPairs. List<List<_ShellObjectPair>> categories = null; removedList = null; ICustomDestinationList destinationList = CLSID.CoCreateInstance<ICustomDestinationList>(CLSID.DestinationList); try { // Even though we're not exposing Shell's AppModelId concept, we'll still respect it // since it's easy to clients to p/invoke to set it for Application and Window, but not for JumpLists string appId = _RuntimeId; if (!string.IsNullOrEmpty(appId)) { destinationList.SetAppID(appId); } // The number ot items visible on a jump list is a user setting. Shell doesn't reject items based on overflow. // We don't bother checking against it because the app can query the setting and manage overflow based on it // if they really care. We'll happily add too many items with the hope that if the user changes the setting // items will be recovered from the overflow. uint slotsVisible; Guid removedIid = new Guid(IID.ObjectArray); var objectsRemoved = (IObjectArray)destinationList.BeginList(out slotsVisible, ref removedIid); // Keep track of the items that were previously removed by the user. // We don't want to pend any items that are contained in this list. // It's possible that this contains null JumpItems when we were unable to do the conversion. removedList = GenerateJumpItems(objectsRemoved); // Keep track of the items that have been successfully pended. // IMPORTANT: Ensure at the end of this that if the list is applied again that it would // result in items being added to the JumpList in the same order. // This doesn't mean that they'll be ordered the same as how the user added them // (e.g. categories will be coalesced), but the categories should appear in the same order. // Since when we call AddCategory we're doing it in reverse order, AddCategory augments // the items in the list in reverse as well. At the end the final list is reversed. successList = new List<JumpItem>(JumpItems.Count); // Keep track of the items that we couldn't pend, and why. rejectedList = new List<_RejectedJumpItemPair>(JumpItems.Count); // Need to group the JumpItems based on their categories. // The special "Tasks" category doesn't actually have a name and should be first so it's unconditionally added. categories = new List<List<_ShellObjectPair>>() { new List<_ShellObjectPair>() }; // This is not thread-safe. // We're traversing the original list so we're vulnerable to another thread modifying it during the enumeration. foreach (var jumpItem in JumpItems) { if (jumpItem == null) { // App added a null jump item? Just go through the normal failure mechanisms. rejectedList.Add(new _RejectedJumpItemPair { JumpItem = jumpItem, Reason = JumpItemRejectionReason.InvalidItem }); continue; } object shellObject = null; try { shellObject = GetShellObjectForJumpItem(jumpItem); // If for some reason we couldn't create the item add it to the rejected list. if (shellObject == null) { rejectedList.Add(new _RejectedJumpItemPair { Reason = JumpItemRejectionReason.InvalidItem, JumpItem = jumpItem }); continue; } // Don't add this item if it's in the list of items previously removed by the user. if (ListContainsShellObject(removedList, shellObject)) { rejectedList.Add(new _RejectedJumpItemPair { Reason = JumpItemRejectionReason.RemovedByUser, JumpItem = jumpItem }); continue; } var shellMap = new _ShellObjectPair { JumpItem = jumpItem, ShellObject = shellObject }; if (string.IsNullOrEmpty(jumpItem.CustomCategory)) { // No custom category, so add to the Tasks list. categories[0].Add(shellMap); } else { // Find the appropriate category and add to that list. // If it doesn't exist, add a new category for it. bool categoryExists = false; foreach (var list in categories) { // The first item in the category list can be used to check the name. if (list.Count > 0 && list[0].JumpItem.CustomCategory == jumpItem.CustomCategory) { list.Add(shellMap); categoryExists = true; break; } } if (!categoryExists) { categories.Add(new List<_ShellObjectPair>() { shellMap }); } } // Shell interface is now owned by the category list. shellObject = null; } finally { Utility.SafeRelease(ref shellObject); } } // Jump List categories get added top-down, except for "Tasks" which is special and always at the bottom. // We want the Recent/Frequent to always be at the top so they get added first. // Logically the categories are added bottom-up, but their contents are top-down, // so we reverse the order we add the categories to the destinationList. // To preserve the item ordering AddCategory also adds items in reverse. // We need to reverse the final list when everything is done. categories.Reverse(); if (ShowFrequentCategory) { destinationList.AppendKnownCategory(KDC.FREQUENT); } if (ShowRecentCategory) { destinationList.AppendKnownCategory(KDC.RECENT); } // Now that all the JumpItems are grouped add them to the custom destinations list. foreach (List<_ShellObjectPair> categoryList in categories) { if (categoryList.Count > 0) { string categoryHeader = categoryList[0].JumpItem.CustomCategory; AddCategory(destinationList, categoryHeader, categoryList, successList, rejectedList); } } destinationList.CommitList(); // Swap the current list with what we were able to successfully place into the JumpList. // Reverse it first to ensure that the items are in a repeatable order. successList.Reverse(); } finally { // Deterministically release native resources. Utility.SafeRelease(ref destinationList); if (categories != null) { foreach (List<_ShellObjectPair> list in categories) { _ShellObjectPair.ReleaseShellObjects(list); } } // Note that this only clears the ShellObjects, not the JumpItems. // We still need the JumpItems out of this list for the JumpItemsRemovedByUser event. _ShellObjectPair.ReleaseShellObjects(removedList); } }
private void _BuildShellLists(out List <JumpItem> successList, out List <_RejectedJumpItemPair> rejectedList, out List <_ShellObjectPair> removedList) { List <List <_ShellObjectPair> > categories = null; removedList = null; var destinationList = CLSID.CoCreateInstance <ICustomDestinationList>(CLSID.DestinationList); try { var appId = _RuntimeId; if (!string.IsNullOrEmpty(appId)) { destinationList.SetAppID(appId); } uint slotsVisible; var removedIid = new Guid(IID.ObjectArray); var objectsRemoved = (IObjectArray)destinationList.BeginList(out slotsVisible, ref removedIid); removedList = GenerateJumpItems(objectsRemoved); successList = new List <JumpItem>(JumpItems.Count); rejectedList = new List <_RejectedJumpItemPair>(JumpItems.Count); categories = new List <List <_ShellObjectPair> > { new List <_ShellObjectPair>() }; foreach (var jumpItem in JumpItems) { if (jumpItem == null) { rejectedList.Add(new _RejectedJumpItemPair { JumpItem = jumpItem, Reason = JumpItemRejectionReason.InvalidItem }); continue; } object shellObject = null; try { shellObject = GetShellObjectForJumpItem(jumpItem); if (shellObject == null) { rejectedList.Add(new _RejectedJumpItemPair { Reason = JumpItemRejectionReason.InvalidItem, JumpItem = jumpItem }); continue; } if (ListContainsShellObject(removedList, shellObject)) { rejectedList.Add(new _RejectedJumpItemPair { Reason = JumpItemRejectionReason.RemovedByUser, JumpItem = jumpItem }); continue; } var shellMap = new _ShellObjectPair { JumpItem = jumpItem, ShellObject = shellObject }; if (string.IsNullOrEmpty(jumpItem.CustomCategory)) { categories[0].Add(shellMap); } else { var categoryExists = false; foreach (var list in categories) { if (list.Count > 0 && list[0].JumpItem.CustomCategory == jumpItem.CustomCategory) { list.Add(shellMap); categoryExists = true; break; } } if (!categoryExists) { categories.Add(new List <_ShellObjectPair> { shellMap }); } } shellObject = null; } finally { Utility.SafeRelease(ref shellObject); } } categories.Reverse(); if (ShowFrequentCategory) { destinationList.AppendKnownCategory(KDC.FREQUENT); } if (ShowRecentCategory) { destinationList.AppendKnownCategory(KDC.RECENT); } foreach (var categoryList in categories) { if (categoryList.Count > 0) { var categoryHeader = categoryList[0].JumpItem.CustomCategory; AddCategory(destinationList, categoryHeader, categoryList, successList, rejectedList); } } destinationList.CommitList(); successList.Reverse(); } finally { Utility.SafeRelease(ref destinationList); if (categories != null) { foreach (var list in categories) { _ShellObjectPair.ReleaseShellObjects(list); } } _ShellObjectPair.ReleaseShellObjects(removedList); } }
private void ApplyList() { Debug.Assert(_initializing == false); Verify.IsApartmentState(ApartmentState.STA); // We don't want to force applications to conditionally check this before constructing a JumpList, // but if we're not on 7 then this isn't going to work. Fail fast. if (!Utilities.IsOSWindows7OrNewer) { RejectEverything(); return; } List <JumpItem> successList; List <_RejectedJumpItemPair> rejectedList; // Declare these outside the try block so we can cleanup native resources in the _ShellObjectPairs. List <List <_ShellObjectPair> > categories = null; List <_ShellObjectPair> removedList = null; var destinationList = (ICustomDestinationList)Activator.CreateInstance(Type.GetTypeFromCLSID(new Guid(CLSID.DestinationList))); try { // Even though we're not exposing Shell's AppModelId concept, we'll still respect it // since it's easy to clients to p/invoke to set it for Application and Window, but not for JumpLists string appId = _RuntimeId; if (!string.IsNullOrEmpty(appId)) { destinationList.SetAppID(appId); } // The number ot items visible on a jump list is a user setting. Shell doesn't reject items based on overflow. // We don't bother checking against it because the app can query the setting and manage overflow based on it // if they really care. We'll happily add too many items with the hope that if the user changes the setting // items will be recovered from the overflow. uint slotsVisible; Guid removedIid = new Guid(IID.ObjectArray); var objectsRemoved = (IObjectArray)destinationList.BeginList(out slotsVisible, ref removedIid); // Keep track of the items that were previously removed by the user. // We don't want to pend any items that are contained in this list. // It's possible that this contains null JumpItems when we were unable to do the conversion. removedList = GenerateJumpItems(objectsRemoved); // Keep track of the items that have been successfully pended. // IMPORTANT: Ensure at the end of this that if the list is applied again that it would // result in items being added to the JumpList in the same order. // This doesn't mean that they'll be ordered the same as how the user added them // (e.g. categories will be coalesced), but the categories should appear in the same order. // Since when we call AddCategory we're doing it in reverse order, AddCategory augments // the items in the list in reverse as well. At the end the final list is reversed. successList = new List <JumpItem>(JumpItems.Count); // Keep track of the items that we couldn't pend, and why. rejectedList = new List <_RejectedJumpItemPair>(JumpItems.Count); // Need to group the JumpItems based on their categories. // The special "Tasks" category doesn't actually have a name and should be first so it's unconditionally added. categories = new List <List <_ShellObjectPair> >() { new List <_ShellObjectPair>() }; // This is not thread-safe. // We're traversing the original list so we're vulnerable to another thread modifying it during the enumeration. foreach (var jumpItem in JumpItems) { if (jumpItem == null) { // App added a null jump item? Just go through the normal failure mechanisms. rejectedList.Add(new _RejectedJumpItemPair { JumpItem = jumpItem, Reason = JumpItemRejectionReason.InvalidItem }); continue; } object shellObject = null; try { shellObject = GetShellObjectForJumpItem(jumpItem); // If for some reason we couldn't create the item add it to the rejected list. if (shellObject == null) { rejectedList.Add(new _RejectedJumpItemPair { Reason = JumpItemRejectionReason.InvalidItem, JumpItem = jumpItem }); continue; } // Don't add this item if it's in the list of items previously removed by the user. if (ListContainsShellObject(removedList, shellObject)) { rejectedList.Add(new _RejectedJumpItemPair { Reason = JumpItemRejectionReason.RemovedByUser, JumpItem = jumpItem }); continue; } var shellMap = new _ShellObjectPair { JumpItem = jumpItem, ShellObject = shellObject }; if (string.IsNullOrEmpty(jumpItem.CustomCategory)) { // No custom category, so add to the Tasks list. categories[0].Add(shellMap); } else { // Find the appropriate category and add to that list. // If it doesn't exist, add a new category for it. bool categoryExists = false; foreach (var list in categories) { // The first item in the category list can be used to check the name. if (list.Count > 0 && list[0].JumpItem.CustomCategory == jumpItem.CustomCategory) { list.Add(shellMap); categoryExists = true; break; } } if (!categoryExists) { categories.Add(new List <_ShellObjectPair>() { shellMap }); } } // Shell interface is now owned by the category list. shellObject = null; } finally { Utilities.SafeRelease(ref shellObject); } } // Jump List categories get added top-down, except for "Tasks" which is special and always at the bottom. // We want the Recent/Frequent to always be at the top so they get added first. // Logically the categories are added bottom-up, but their contents are top-down, // so we reverse the order we add the categories to the destinationList. // To preserve the item ordering AddCategory also adds items in reverse. // We need to reverse the final list when everything is done. categories.Reverse(); if (ShowFrequentCategory) { destinationList.AppendKnownCategory(KDC.FREQUENT); } if (ShowRecentCategory) { destinationList.AppendKnownCategory(KDC.RECENT); } // Now that all the JumpItems are grouped add them to the custom destinations list. foreach (List <_ShellObjectPair> categoryList in categories) { if (categoryList.Count > 0) { string categoryHeader = categoryList[0].JumpItem.CustomCategory; AddCategory(destinationList, categoryHeader, categoryList, successList, rejectedList); } } destinationList.CommitList(); } catch { // It's not okay to throw an exception here. If Shell is rejecting the JumpList for some reason // we don't want to be responsible for the app throwing an exception in its startup path. // For common use patterns there isn't really any user code on the stack, so there isn't // an opportunity to catch this in the app. // We can instead handle this. // Try to notify the developer if they're hitting this. if (TraceShell.IsEnabled) { TraceShell.Trace(TraceEventType.Error, TraceShell.RejectingJumpItemsBecauseCatastrophicFailure); } RejectEverything(); return; } finally { // Deterministically release native resources. Utilities.SafeRelease(ref destinationList); if (categories != null) { foreach (List <_ShellObjectPair> list in categories) { _ShellObjectPair.ReleaseShellObjects(list); } } // Note that this only clears the ShellObjects, not the JumpItems. // We still need the JumpItems out of this list for the JumpItemsRemovedByUser event. _ShellObjectPair.ReleaseShellObjects(removedList); } // Swap the current list with what we were able to successfully place into the JumpList. // Reverse it first to ensure that the items are in a repeatable order. successList.Reverse(); _jumpItems = successList; // Raise the events for rejected and removed. EventHandler <JumpItemsRejectedEventArgs> rejectedHandler = JumpItemsRejected; EventHandler <JumpItemsRemovedEventArgs> removedHandler = JumpItemsRemovedByUser; if (rejectedList.Count > 0 && rejectedHandler != null) { var items = new List <JumpItem>(rejectedList.Count); var reasons = new List <JumpItemRejectionReason>(rejectedList.Count); foreach (_RejectedJumpItemPair rejectionPair in rejectedList) { items.Add(rejectionPair.JumpItem); reasons.Add(rejectionPair.Reason); } rejectedHandler(this, new JumpItemsRejectedEventArgs(items, reasons)); } if (removedList.Count > 0 && removedHandler != null) { var items = new List <JumpItem>(removedList.Count); foreach (_ShellObjectPair shellMap in removedList) { // It's possible that not every shell object could be converted to a JumpItem. if (shellMap.JumpItem != null) { items.Add(shellMap.JumpItem); } } if (items.Count > 0) { removedHandler(this, new JumpItemsRemovedEventArgs(items)); } } }
private void _BuildShellLists(out List <JumpItem> successList, out List <_RejectedJumpItemPair> rejectedList, out List <_ShellObjectPair> removedList) { List <List <_ShellObjectPair> > list = null; removedList = null; Standard.ICustomDestinationList cdl = Standard.CLSID.CoCreateInstance <Standard.ICustomDestinationList>("77f10cf0-3db5-4966-b520-b7c54fd35ed6"); try { uint num; string str = _RuntimeId; if (!string.IsNullOrEmpty(str)) { cdl.SetAppID(str); } Guid riid = new Guid("92CA9DCD-5622-4bba-A805-5E9F541BD8C9"); Standard.IObjectArray shellObjects = (Standard.IObjectArray)cdl.BeginList(out num, ref riid); removedList = GenerateJumpItems(shellObjects); successList = new List <JumpItem>(this.JumpItems.Count); rejectedList = new List <_RejectedJumpItemPair>(this.JumpItems.Count); list = new List <List <_ShellObjectPair> > { new List <_ShellObjectPair>() }; foreach (JumpItem item in this.JumpItems) { if (item == null) { _RejectedJumpItemPair pair = new _RejectedJumpItemPair { JumpItem = item, Reason = JumpItemRejectionReason.InvalidItem }; rejectedList.Add(pair); } else { object shellObject = null; try { shellObject = GetShellObjectForJumpItem(item); if (shellObject == null) { _RejectedJumpItemPair pair2 = new _RejectedJumpItemPair { Reason = JumpItemRejectionReason.InvalidItem, JumpItem = item }; rejectedList.Add(pair2); } else if (ListContainsShellObject(removedList, shellObject)) { _RejectedJumpItemPair pair3 = new _RejectedJumpItemPair { Reason = JumpItemRejectionReason.RemovedByUser, JumpItem = item }; rejectedList.Add(pair3); } else { _ShellObjectPair pair4 = new _ShellObjectPair { JumpItem = item, ShellObject = shellObject }; if (string.IsNullOrEmpty(item.CustomCategory)) { list[0].Add(pair4); } else { bool flag = false; foreach (List <_ShellObjectPair> list3 in list) { if ((list3.Count > 0) && (list3[0].JumpItem.CustomCategory == item.CustomCategory)) { list3.Add(pair4); flag = true; break; } } if (!flag) { list.Add(new List <_ShellObjectPair> { pair4 }); } } shellObject = null; } } finally { Standard.Utility.SafeRelease <object>(ref shellObject); } } } list.Reverse(); if (this.ShowFrequentCategory) { cdl.AppendKnownCategory(Standard.KDC.FREQUENT); } if (this.ShowRecentCategory) { cdl.AppendKnownCategory(Standard.KDC.RECENT); } foreach (List <_ShellObjectPair> list5 in list) { if (list5.Count > 0) { string customCategory = list5[0].JumpItem.CustomCategory; AddCategory(cdl, customCategory, list5, successList, rejectedList); } } cdl.CommitList(); successList.Reverse(); } finally { Standard.Utility.SafeRelease <Standard.ICustomDestinationList>(ref cdl); if (list != null) { foreach (List <_ShellObjectPair> list7 in list) { _ShellObjectPair.ReleaseShellObjects(list7); } } _ShellObjectPair.ReleaseShellObjects(removedList); } }