I'm trying to understand how to manage a transition between two collections in pivotviewer. Collections have the same images, it's just that one collection is processed. I want to have one collection vanishing into the other.
I know how to define different templates that vanishes one into the other by fixing the maxwidth. For example once you zoom over 300 px then you have your new template until you reach 500 px, etc. The code I use to bind the collection I've loaded in the code behind is like this:
<pv:PivotViewerItemTemplate x:Key="firstTemplate" MaxWidth="300">
<!-- template layout -->
<pv:PivotViewerMultiScaleSubImageHost CollectionSource="{Binding [VisualCollectionSource][0] }" ImageId="{Binding [VisualImageId][0]}" />
<!-- template layout -->
</pv:PivotViewerItemTemplate>
Is there a solution like this I can adopt? What's the best practice for it?
Here's an example of keeping the overlap between loaded CXML collections, instead of replacing the entire collection. Since there are animations when adding and removing objects, it looks pretty nice. Useful when requesting more/partial data from the server/backend. (Of course, this has nothing to do with "fading collections/items" when zooming.)
The most interesting code would be in KeepIntersection(this ICollection<PivotViewerItem> currentItems, CxmlCollectionSource newItems), which modifies the collection by adding and removing only the differences from the old and the new collections.
Based on Silverlight 5 PivotViewer's ObservableCollection as per Tony Champions's and Chris Arnold's tutorials/posts.
MainPageViewModel.cs
private void CxmlCollectionSource_StateChanged(object sender, CxmlCollectionStateChangedEventArgs e)
{
// TODO: check other states
switch (e.NewState)
{
case CxmlCollectionState.Loaded:
{
var collection = sender as CxmlCollectionSource;
Debug.Assert(collection != null, "collection != null");
// TODO: don't add/remove, replace the entire list after diffing
if (!this.pivotProperties.Any())
{
// TODO: diffing algorithm for properties, minimal changes
foreach (var pivotViewerProperty in collection.ItemProperties)
{
this.pivotProperties.Add(pivotViewerProperty);
}
}
this.pivotViewerItems.KeepIntersection(collection);
break;
}
}
}
ICollection{PivotViewerItem}Extensions.cs
namespace SilverlightPivotViewer.Extensions
{
#region Using directives
using System.Collections.Generic;
using System.Linq;
using System.Windows.Controls.Pivot;
#endregion
public static class ICollectionPivotViewerItemExtensions
{
#region Public Methods and Operators
public static void KeepIntersection(
this ICollection<PivotViewerItem> currentItems, CxmlCollectionSource newItems)
{
RemoveCurrentUniqueItems(currentItems, newItems);
AddNewUniqueItems(currentItems, newItems);
}
#endregion
#region Methods
private static void AddNewUniqueItems(ICollection<PivotViewerItem> currentItems, CxmlCollectionSource newItems)
{
IEnumerable<PivotViewerItem> onlyInNewCollection =
newItems.Items.Where(pivotViewerItem => currentItems.All(i => i.Id != pivotViewerItem.Id));
foreach (var pivotViewerItem in onlyInNewCollection)
{
currentItems.Add(pivotViewerItem);
}
}
private static void RemoveCurrentUniqueItems(
ICollection<PivotViewerItem> currentItems, CxmlCollectionSource newItems)
{
IEnumerable<PivotViewerItem> onlyInCurrentCollection =
currentItems.Where(pivotViewerItem => newItems.Items.All(i => i.Id != pivotViewerItem.Id));
// Need to produce a list, otherwise it will crash (concurrent looping and editing the IEnumerable, or something related)
var onlyInCurrentCollectionList = onlyInCurrentCollection.ToList();
foreach (var pivotViewerItem in onlyInCurrentCollectionList)
{
currentItems.Remove(pivotViewerItem);
}
}
#endregion
}
}
Coded custom diffing functions, but I'm sure someone has an awesome library doing just that.
Thought of adding diffing for facet categories too, but it's not built for it. I guess the recommended way is to make sure the client knows about all available categories, so they can be used for filtering.
Related
Using the Sitecore item:saved event handler, I am trying to add items in other languages. So if you say, add a item in one language, it should automatically add the item in the other given languages when saved.
Right now, I can get all the languages from the master database. The problem is when adding a new version of the new language, it triggers itself and therefore recursive adds "infinite" new elements until it crashes. How is it possible to bypass this?
public void OnItemSaved(object sender, EventArgs args)
{
Item savedItem = Event.ExtractParameter(args, 0) as Item;
if (savedItem.Versions.Count == 1)
{
// Allow only non null items and allow only items from the master database |
if (savedItem != null && savedItem.Database.Name.ToLower() == "master")
{
// Do some kind of template validation to limit only the items
if (savedItem.TemplateID == ID.Parse("{template id}"))
{
// Get all the installed languages from the master database
var installedLanguages = LanguageManager.GetLanguages(Database.GetDatabase("master"));
// Go through every language in the list
foreach (var language in installedLanguages)
{
// Copy item to the new language
if (savedItem.Language.Name != language.Name)
{
using (new LanguageSwitcher(language.Name))
{
// Save the new item, but it fails since it triggers itself (the event)
savedItem.Versions.AddVersion();
}
}
}
}
}
}
Try this code:
foreach (var language in installedLanguages)
{
if (savedItem.Language.Name != language.Name)
{
var otherLanguageItem = savedItem.Database.GetItem(savedItem.ID, language);
if (otherLanguageItem.Versions.Count == 0)
{
otherLanguageItem.Versions.AddVersion();
}
}
}
It's been quite a while since I have done any Sitecore work and I don't currently have access to a Sitecore instance for testing out a code sample, but one way to approach this would be to put a collection of updated item ids on the context. The approach (in pseudo code) could be inserted immediately after your template check (to keep less expensive checks earlier than more expensive ones) and would go something like:
- retrieve collection of updated items from context or create
- if collection contains current item id, return immediately
- add current item id to collection
I'd be happy to provide more clarity on how this would look syntactically if needed, but honestly if I write something in C# I prefer to be able to actually see it work
This is an attempt to expand on this question. In my WPF program I've been cloning tabItems by using an XamlWriter in a function called TrycloneElement. I originally found this function here, but the function can also be viewed in the link to my previous question.
Now that I am beginning to worry about functionality inside my program, I found that the TrycloneElement function does not replicate any code-behind functionality assigned to the tabItem that it is cloning.
Because of High Core's link and comment on my earlier question I decided to start implementing functionality on my tabItems through Data Binding with my ViewModel.
Here is a sample of a command that I've implemented:
public viewModel()
{
allowReversing = new Command(allowReversing_Operations);
}
public Command AllowReversing
{
get { return allowReversing; }
}
private Command allowReversing;
private void allowReversing_Operations()
{
//Query for Window1
var mainWindow = Application.Current.Windows
.Cast<Window1>()
.FirstOrDefault(window => window is Window1) as Window1;
if (mainWindow.checkBox1.IsChecked == true) //Checked
{
mainWindow.checkBox9.IsEnabled = true;
mainWindow.groupBox7.IsEnabled = true;
}
else //UnChecked
{
mainWindow.checkBox9.IsEnabled = false;
mainWindow.checkBox9.IsChecked = false;
mainWindow.groupBox7.IsEnabled = false;
}
}
*NOTE: I know that I cheated and interacted directly with my View in the above code, but I wasn't sure how else to run those commands. If it is a problem, or there is another way, please show me how I can run those same commands without interacting with the View like I did.
Now to the question:
After changing my code and adding the commands to my ViewModel, the TrycloneElement function no longer works. At run time during the tab clone I receive an XamlParseException on line, object x = XamlReader.Load(xmlReader); that reads:
I'm fine with ditching the function if there is a better way and I don't need it anymore. But ultimately, how do I take a tabItem's design and functionality and clone it? (Please keep in mind that I really am trying to correct my structure)
Thank you for your help.
Revision of Leo's answer
This is the current version of Leo's answer that I have compiling. (There were some syntax errors)
public static IList<DependencyProperty> GetAllProperties(DependencyObject obj)
{
return (from PropertyDescriptor pd in TypeDescriptor.GetProperties(obj, new Attribute[] { new PropertyFilterAttribute(PropertyFilterOptions.SetValues) })
select DependencyPropertyDescriptor.FromProperty(pd)
into dpd
where dpd != null
select dpd.DependencyProperty).ToList();
}
public static void CopyPropertiesFrom(this FrameworkElement controlToSet,
FrameworkElement controlToCopy)
{
foreach (var dependencyValue in GetAllProperties(controlToCopy)
.Where((item) => !item.ReadOnly)
.ToDictionary(dependencyProperty => dependencyProperty, controlToCopy.GetValue))
{
controlToSet.SetValue(dependencyValue.Key, dependencyValue.Value);
}
}
Here is my example of a properly-implemented dynamic TabControl in WPF.
The main idea is that each Tab Item is a separate widget that contains its own logic and data, which is handled by the ViewModel, while the UI does what the UI must do: show data, not contain data.
The bottom line is that all data and functionality is managed at the ViewModel / Model levels, and since the TabControl is bound to an ObservableCollection, you simply add another element to that Collection whenever you need to add a new Tab.
This removes the need for "cloning" the UI or do any other weird manipulations with it.
1.) To fix that XamlParseException, make sure you have a public constructor like an empty one, you probably defined a constructor and when you tried to serialize that object and deserialize it can't. You have to explicitly add the default constructor.
2.) I don't like the word clone, but I'd say, when they want to copy. I'll manually create a new tab item control then do reflection on it.
I have this code that I made
public static IList<DependencyProperty> GetAllProperties(DependencyObject obj)
{
return (from PropertyDescriptor pd in TypeDescriptor.GetProperties(obj, new Attribute[] {new PropertyFilterAttribute(PropertyFilterOptions.SetValues)})
select DependencyPropertyDescriptor.FromProperty(pd)
into dpd where dpd != null select dpd.DependencyProperty).ToList();
}
public static void CopyPropertiesFrom(this FrameworkElement controlToSet,
FrameworkElement controlToCopy)
{
foreach (var dependencyValue in GetAllProperties(controlToCopy)
.Where((item) => !item.ReadOnly))
.ToDictionary(dependencyProperty => dependencyProperty, controlToCopy.GetValue))
{
controlToSet.SetValue(dependencyValue.Key, dependencyValue.Value);
}
}
So it would be like
var newTabItem = new TabItem();
newTabItem.CopyPropertiesFrom(masterTab);
I have controls being dynamically added and recreated on a web dashboard. I have been attempting to reduce my usage of Page.FindControl; I found it to be slow with large amounts of dynamically created content on Page.
In my attempts, I created a RegenerationManager singleton which is now in charge of regenerating my dynamic content. Whenever it regenerates an object, it stores the object in a list. This manager keeps several of these lists based on type of object.
Later, when I need to fetch an object by ID, I go to my regeneration manager to retrieve the object.
E.G:
public class RegenerationManager
{
private static readonly ILog Logger = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);
private static readonly RegenerationManager instance = new RegenerationManager();
private RegenerationManager() { }
public static RegenerationManager Instance
{
get { return instance; }
}
public List<CormantRadPane> RegeneratedPanes = new List<CormantRadPane>();
public List<CormantRadDockZone> RegeneratedDockZones = new List<CormantRadDockZone>();
/// <summary>
/// Recreates all the dynamically made DockZones.
/// </summary>
public void RegenerateDockZones()
{
Logger.Info("Regenerating dock zones.");
foreach (KeyValuePair<string, RadDockZoneSetting> dockZoneState in RadControlManager.GetStates<SerializableDictionary<string, RadDockZoneSetting>>())
{
try
{
RadDockZoneSetting dockZoneSetting = dockZoneState.Value as RadDockZoneSetting;
Logger.Info(String.Format("Loading state data for dock zone with setting ID: {0}", dockZoneSetting.ID));
CormantRadDockZone dockZone = new CormantRadDockZone(dockZoneSetting);
RegeneratedDockZones.Add(dockZone);
CormantRadPane pane = RegeneratedPanes.First(regeneratedPane => regeneratedPane.ID == dockZoneSetting.ParentID);
pane.Controls.Add(dockZone);
}
catch (Exception exception)
{
Logger.ErrorFormat("Error regenerating dock zones. Reason: {0}", exception.Message);
}
}
}
}
/// <summary>
/// Creates the RadDock + Contents when dropping a graph onto the page.
/// </summary>
/// <param name="sender"> The RadListBox with an element being dropped from inside it. </param>
/// <param name="e"> Information about the drop such as target and what is being dropped. </param>
protected void RadListBox_Dropped(object sender, RadListBoxDroppedEventArgs e)
{
CormantRadDockZone dockZoneOld = Utilities.FindControlRecursive(Page, e.HtmlElementID) as CormantRadDockZone;
CormantRadDockZone dockZone = RegenerationManager.Instance.RegeneratedDockZones.First(regeneratedDockZone => regeneratedDockZone.ID == e.HtmlElementID);
if (!object.Equals(dockZone, null) && !dockZone.Docks.Any())
{
RadSlidingPane slidingPane = ((sender as RadListBox).Parent as RadSlidingPane);
CormantReport report = new CormantReport(int.Parse(e.SourceDragItems[0].Value), e.SourceDragItems[0].Text, slidingPane.Title);
CormantRadDock dock = new CormantRadDock(report);
dock.CreateContent();
dockZone.Controls.Add(dock);
}
}
Now, I thought this was a pretty good solution, but upon testing I found that the CormantRadDock being added to dockZone does not show up on my web dashboard, but if I added it to dockZoneOld that it does.
Looking at the two objects I did not notice any properties different about the two, but obviously there is something.
Is my idea possible to implement?
EDIT: I suspect the problem is with Telerik's controls. I have an idea I'll report back in a bit.
EDIT: It was indeed a Telerik thing. I needed to go through their RadDockLayout.RegisteredDockZone's list instead of keeping my own managed one.
The key thing to do here is debug your code and put a breakpoint on this line:
CormantRadDockZone dockZone = RegenerationManager.Instance.RegeneratedDockZones.First(regeneratedDockZone =>regeneratedDockZone.ID == e.HtmlElementID);`
Then do a quickwatch on the contents of RegenerationManager.Instance.RegeneratedDockZones to see if that list has any elements at in it. So here we're checking the basics - does the list have any items in it at all?
If there are items in there, you need to figure out why the specific element your looking for is not in the list. The list must be working, as there are items in there, but this specific element has not been added.
If you're 100% that the element is being added to the list, then it could be something to do with the singleton pattern your using. To be honest, it looks OK to me, but I'm no expert on the singleton design pattern.
Sorry I can't be of more help.
EDIT:
One thing which might help is to refactor the class a little bit:
private IList<CormantRadPane> _regeneratedPanes;
private IList<CormantRadDockZone> _regeneratedDockZones;
public IList<CormantRadPane> RegeneratedPanes
{
get
{
if(_regeneratedPanes == null)
{
_regeneratedPanes = new List<CormantRadPane>()
}
return _regeneratedPanes
}
}
public IList<CormantRadDockZone> RegeneratedDockZones
{
get
{
if(_regeneratedDockZones == null)
{
_regeneratedDockZones = new List<CormantRadDockZone>()
}
return _regeneratedDockZones
}
}
Here I'm using properties to return the lists, rather then giving people direct access to the list members.
This could help you with debugging your code.
I'm using a PagedCollectionView to bind an ObservableCollection<T> to a DataGrid in my Silverlight app. In this case, the source collection can incur an unbounded number of updates during its lifespan. It seems, however, that if I'm using a PropertyGroupDescription to group the elements in the DataGrid then I need to reapply that grouping using PagedCollectionView.GroupDescriptions.Add(...) every time the source collection is updated with an element that doesn't fit into an existing grouping. Is there any way to make the groupings refresh/recalculate automatically?
public ObservableCollection<DataItem> DataItems
{
get { return _dataItems; }
private set { _dataItems = value; }
}
public PagedCollectionView ItemsView
{
get { return _itemsView; }
private set { _itemsView = value; }
}
public MyGridControl()
{
// Initialize data members
DataItems = new ObservableCollection<DataItem>();
ItemsView = new PagedCollectionView(GridDataItems);
Loaded += new RoutedEventHandler(MyGridControl_Loaded);
}
private void MyGridControl_Loaded(object sender, RoutedEventArgs e)
{
_myGrid.ItemsSource = ItemsView;
}
public void RegisterDataItem(DataItem item)
{
DataItems.Add(item);
/* To get grouping to work like I want, I have to add this:
* ItemsView.GroupDescriptions.Clear();
* ItemsView.GroupDescriptions.Add(new PropertyGroupDescription("GroupName"));
*/
}
public void UnregisterError(DataItem item)
{
DataItem.Remove(item);
}
Thanks, and let me know if I can provide any additional information.
So, as far as I can tell, the grouping operation appears to be a once-off operation that is performed on the data as it exists at the time that the PagedCollectionView.GroupDescriptions collection is manipulated. It seems that the desired groupings do indeed need to be re-applied when the source collection is changed.
I did find one alternative approach for my specific case. Since I'm using an ObservableCollection for the source collection of the view, I can wire up something to the CollectionChanged event of that source collection wherein the PagedCollectionView.GroupDescriptions collection gets cleared and the desired GroupDescription is then reapplied. This doesn't seem totally compliant with good OO practices, nor is it usable if the source collection for the view doesn't implement INotifyCollectionChanged.
I'll leave this open a little longer in case anyone can offer another approach, otherwise I'll just concede.
Thanks again.
Looking at the code in Reflector, the Groupings are correctly processed but if you use view in a DataGrid with IsReadOnly == true you won't see the groupings reflected while changing the source collection.
I have two classes: Employee and EmployeeGridViewAdapter. Employee is composed of several complex types. EmployeeGridViewAdapter wraps a single Employee and exposes its members as a flattened set of system types so a DataGridView can handle displaying, editing, etc.
I'm using VS's builtin support for turning a POCO into a data source, which I then attach to a BindingSource object. When I attach the DataGridView to the BindingSource it creates the expected columns and at runtime I can perform the expected CRUD operations. All is good so far.
The problem is the collection of adapters and the collection of employees aren't being synchronized. So all the employees I create an runtime never get persisted. Here's a snippet of the code that generates the collection of EmployeeGridViewAdapter's:
var employeeCollection = new List<EmployeeGridViewAdapter>();
foreach (var employee in this.employees)
{
employeeCollection.Add(new EmployeeGridViewAdapter(employee));
}
this.view.Employees = employeeCollection;
Pretty straight forward but I can't figure out how to synchronize changes back to the original collection. I imagine edits are already handled because both collections reference the same objects but creating new employees and deleting employees aren't happening so I can't be sure.
You could also considering using System.Collections.ObjectModel.ObservableCollection and wiring up it's CollectionChanged event. It could look something like this.
ObservableCollection<EmployeeAdapter> observableEmployees =
new ObservableCollection<EmployeeAdapter>();
foreach (Employee emp in employees)
{
observableEmployees.Add(new EmployeeAdapter(emp));
}
observableEmployees.CollectionChanged +=
(object sender, NotifyCollectionChangedEventArgs e) =>
{
ObservableCollection<EmployeeAdapter> views =
sender as ObservableCollection<EmployeeAdapter>;
if (views == null)
return;
switch (e.Action)
{
case NotifyCollectionChangedAction.Add:
foreach (EmployeeAdapter view in e.NewItems)
{
if (!employees.Contains(view.Employee))
employees.Add(view.Employee);
}
break;
case NotifyCollectionChangedAction.Remove:
foreach (EmployeeAdapter view in e.OldItems)
{
if (employees.Contains(view.Employee))
employees.Remove(view.Employee);
}
break;
default:
break;
}
};
Code assumes the following using statements.
using System.Collections.ObjectModel;
using System.Collections.Specialized;
If you need the IList interface you could also use System.ComponentModel.BindingList and wire up it's ListChanged event. It could look like this.
BindingList<EmployeeAdapter> empViews = new BindingList<EmployeeAdapter>();
foreach (Employee emp in employees)
{
empViews.Add(new EmployeeAdapter(emp));
}
empViews.ListChanged +=
(object sender, ListChangedEventArgs e) =>
{
BindingList<EmployeeAdapter> employeeAdapters =
sender as BindingList<EmployeeAdapter>;
if (employeeAdapters == null)
return;
switch (e.ListChangedType)
{
case ListChangedType.ItemAdded:
EmployeeAdapter added = employeeAdapters[e.NewIndex];
if (!employees.Contains(added.Employee))
employees.Add(added.Employee);
break;
case ListChangedType.ItemDeleted:
EmployeeAdapter deleted = employeeAdapters[e.OldIndex];
if (employees.Contains(deleted.Employee))
employees.Remove(deleted.Employee);
break;
default:
break;
}
};
Code assumes the following using statement.
using System.ComponentModel;
The first problem seems to be that you are creating a new list and data binding to that. When you're adding elements these will be added to the collection but your original employee list remains unmodified.
To avoid this you should either provide a custom collection class that will migrate changes back to the underlying employee list, or wire up the appropriate events (to do the migration on insert/delete) before data binding to it.
To avoid a number of other problems with binding editable collections to grids, you should implement the data binding interfaces, as outlined below. The presence of these interfaces allows the visual controls to notify the underlying collection about actions such as "insert cancelled" (when users aborts entry of a new record), and similarly allows information to flow in the opposite direction (update UI when collection or individual entries change).
First, you'll want to implement at least IEditableObject, INotifyPropertyChanged and IDataErrorInfo on the individual items in a data-bound collection, which in your case would be the EmployeeGridViewAdaper class.
Additionally, you'd want your collection to implement ITypedList and INotifyCollectionChanged. The BCL contains a BindingList implementation which provides a good starting point for this. Recommend using this instead of the plain List.
I can recommend Data Binding with Windows Forms 2.0 for an exhaustive coverage of this topic.