/// <summary>
        /// Occurs when the element is laid out, rendered, and ready for interaction.
        /// </summary>
        /// <param name="sender">The object where the event handler is attached.</param>
        /// <param name="routedEventArgs">The event data.</param>
        protected virtual void OnLoaded(Object sender, RoutedEventArgs routedEventArgs)
        {
            // This provides a data context for the blotter.
            AssetNetworkItem assetNetworkItem = this.DataContext as AssetNetworkItem;

            this.blotterIdField = assetNetworkItem.EntityId;

            // Data model changes can come in high frequency bursts.  To filter out the events that are relevant for this blotter view, we're going to flatten out
            // the hierarchy and put all the blotter identifiers in a set.  Then, when a given working order row (or any row related to a working order) is
            // modified or deleted, we can use this set as a filter to quickly establish what events need attension and what events can be ignored.
            this.blotterIdSet.Clear();
            this.DiscoverBlotters(DataModel.Blotter.BlotterKey.Find(this.BlotterId));

            // Call the virtual version of this method to initialize the event handlers.  Descendants can override this behavior to add additional tables to watch
            // for additional metadata.  It should be noted that the BlotterId and the expanded list of blotters has been generated and is available to the
            // descendant classes when this virtual method is called.
            this.OnLoaded();

            // The visual elements that that display metadata key/value pairs are initialized here.
            this.metadataField.Clear();
            this.InitializeMetadata();

            // This will calculate the initial values for the displayed metadata.  After this initial pass, in order to prevent scanning all the working orders in
            // all the blotters for every change that comes in, we'll peform delta operations on just the properties that have changed.
            this.CalculateMetadata();

            // This will send a message up to the frame that there is a collection of metadata available.  This will be bound to the DetailBar by the frame and the
            // items will appear hosted in the Detail bar.  These items in the DetailBar are not copies but the elements created above.  Any change to the items
            // above will be reflected immediately in the DetailBar.
            this.RaiseEvent(new ItemsSourceEventArgs(ExplorerFrame.DetailBarChangedEvent, this.metadataField));
        }
        /// <summary>
        /// Invoked when an unhandled DragDrop.DragEnter attached event reaches an element in its route that is derived from this class.
        /// </summary>
        /// <param name="dragEventArgs">The DragEventArgs that contains the event data.</param>
        protected override void OnDrop(DragEventArgs dragEventArgs)
        {
            // Close (and dereference) the window used to display the object being dragged.
            this.dragWindow.Close();
            this.dragWindow = null;

            // Attempt to find the element currently selected in the page.  This item will be the parent of any object dropped onto the surface.
            AssetNetworkItem parentItem = ExplorerHelper.FindExplorerItem(this.DataContext as AssetNetworkItem, this.Source) as AssetNetworkItem;

            if (parentItem != null)
            {
                // We can import files dropped onto the surface.  If the format specifies one or more files dragged from the Windows Explorer, then set call a
                // handler to import each filed based on the file type (that is, the extension).
                String[] paths = dragEventArgs.Data.GetData("FileDrop") as String[];
                if (paths != null)
                {
                    foreach (String path in paths)
                    {
                        DocumentProperty externalObject;
                        if (DirectoryPage.documentProperties.TryGetValue(Path.GetExtension(path).ToLower(), out externalObject))
                        {
                            DirectoryPage.CreateDocument(parentItem, path, externalObject);
                        }
                    }
                }
            }
        }
Beispiel #3
0
        /// <summary>
        /// Handles a change to the interpreted Source URI for this frame.
        /// </summary>
        /// <param name="oldUri">The old Source URI.</param>
        /// <param name="newUri">The new Source URI.</param>
        protected override void OnSourceChanged(Uri oldUri, Uri newUri)
        {
            // The main idea here is that we have a data model that streams in.  There is no 'start' or 'end' of load, so we need to deal with the model as it
            // builds itself. Because the AssetNetworkCollection can build itself incrementally as data loads, we can get into a scenario where the outline of the
            // list is built and all the resources are loaded, but we haven't yet loaded the 'Property Store' table, where the metadata containing the viewer for
            // any given object is kept.  This logic here will hook itself into the current AssetNetworkItem selected and watch for an update to the viewer.  If
            // and when a new viewer should arrive, we'll just poke it in.  This will unhook the previous AssetNetworkItem from watching for the change.
            if (oldUri != null)
            {
                AssetNetworkItem oldItem = ExplorerHelper.FindExplorerItem(this.DataContext as IExplorerItem, oldUri) as AssetNetworkItem;
                if (oldItem != null)
                {
                    oldItem.PropertyChanged -= this.OnItemPropertyChanged;
                }
            }

            // This will allow us to poke a new viewer in should the properties for the current AssetNetworkItem selected be changed by an update from the data
            // model.
            if (newUri != null)
            {
                AssetNetworkItem newItem = ExplorerHelper.FindExplorerItem(this.DataContext as IExplorerItem, newUri) as AssetNetworkItem;
                if (newItem != null)
                {
                    newItem.PropertyChanged += this.OnItemPropertyChanged;
                }
            }
        }
Beispiel #4
0
        /// <summary>
        /// Represents the method that will handle the PropertyChanged event raised when a property is changed on a component.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">A PropertyChangedEventArgs that contains the event data.</param>
        void OnItemPropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
        {
            // In this scenario, we may have constructed an AssetNetworkCollection hierarchy for the application, but not have all the properties loaded, such as
            // the viewer for a given object. When the viewer finally does arrive, or if the viewer is changed dynamically, this will force the new viewer to be the
            // actual URI source of the frame.  This shouldn't be confused with the intepreted source, which is really just a method of Source URI mapping.  If I
            // hadn't been lazy, I might have just tried to use the SourceUri Mapping data structures on this window to map an '/Administrator' into a
            // 'pack://application:,,,/Teraque.AssetNetwork.Navigator;component/DirectoryPage.xaml?Administrator' source.
            AssetNetworkItem assetNetworkItem = sender as AssetNetworkItem;

            if (e.PropertyName == "Viewer")
            {
                this.ActualSource = assetNetworkItem.Viewer;
            }
        }
        /// <summary>
        /// Occurs when the element is laid out, rendered, and ready for interaction.
        /// </summary>
        /// <param name="sender">The object where the event handler is attached.</param>
        /// <param name="routedEventArgs">The event data.</param>
        void OnLoaded(Object sender, RoutedEventArgs routedEventArgs)
        {
            // This provides a data context for the blotter.
            AssetNetworkItem assetNetworkItem = this.DataContext as AssetNetworkItem;

            this.entityIdField = assetNetworkItem.EntityId;

            // Link the page into the data model.
            DataModel.Entity.EntityRowChanged += this.OnEntityRowChanged;

            // This will calculate the initial values for the displayed metadata.
            this.CalculateMetadata();

            // This will send a message up to the frame that there is a collection of metadata available.  This will be bound to the DetailBar by the frame and the
            // items will appear hosted in the Detail bar.  These items in the DetailBar are not copies but the elements created above.  Any change to the items
            // above will be reflected immediately in the DetailBar.
            this.RaiseEvent(new ItemsSourceEventArgs(ExplorerFrame.DetailBarChangedEvent, this.metadata));
        }
Beispiel #6
0
        /// <summary>
        /// Copies the information from the data model into the item.
        /// </summary>
        /// <param name="owner">The owner collection.</param>
        /// <param name="entityRow">The record in the data model that contains the information for this item.</param>
        protected void Copy(AssetNetworkCollection owner, DataModel.EntityRow entityRow)
        {
            // Validate the parameters
            if (owner == null)
            {
                throw new ArgumentNullException("owner");
            }
            if (entityRow == null)
            {
                throw new ArgumentNullException("entityRow");
            }

            // Copy the basic information out of the data model.  These statements will also cause the NotifyPropertyChange event to fire.
            this.DateCreated     = entityRow.CreatedTime;
            this.DateModified    = entityRow.ModifiedTime;
            this.EntityId        = entityRow.EntityId;
            this.Name            = entityRow.Name;
            this.TypeDescription = entityRow.TypeRow.Description;
            this.TypeId          = entityRow.TypeId;

            // Create properties from the metadata associated with this entity.
            foreach (DataModel.PropertyStoreRow propertyStoreRow in entityRow.GetPropertyStoreRows())
            {
                // Copy the viewer property.
                if (propertyStoreRow.PropertyId == PropertyId.Viewer)
                {
                    this.Viewer = new Uri(Encoding.Unicode.GetString(propertyStoreRow.Value as Byte[]), UriKind.RelativeOrAbsolute);
                }

                // Copy the data property.
                if (propertyStoreRow.PropertyId == PropertyId.Data)
                {
                    this.Data = propertyStoreRow.Value as Byte[];
                }
            }

            // This will disassemble the icon and give us the basic image sizes supported by the application framework.
            Dictionary <ImageSize, ImageSource> images = ImageHelper.DecodeIcon(Convert.FromBase64String(entityRow.ImageRow.Image));

            this.SmallImageSource      = images[ImageSize.Small];
            this.MediumImageSource     = images[ImageSize.Medium];
            this.LargeImageSource      = images[ImageSize.Large];
            this.ExtraLargeImageSource = images[ImageSize.ExtraLarge];

            // The next part of the copy operation involves recursing into the children and copying, moving or removing the items to reflect the current hierarchy.
            // This query is the meat of recursing into the hierarchy.  This will create a sorted array of all the child items of this node.
            var children = from entityTreeItem in entityRow.GetEntityTreeRowsByFK_Entity_EntityTree_ParentId()
                           where entityTreeItem.EntityRowByFK_Entity_EntityTree_ChildId.IsContainer == true
                           orderby entityTreeItem.EntityRowByFK_Entity_EntityTree_ChildId.Name
                           select entityTreeItem.EntityRowByFK_Entity_EntityTree_ChildId;

            DataModel.EntityRow[] childArray = children.ToArray <DataModel.EntityRow>();

            // This list is part of a MVVM.  Since this collection is designed to be bound to the user interface, it is important not to wipe it clean and rebuild
            // it each time something changes.  The main idea of this algorithm is to find out what children are new, what children need to be updated and what
            // children need to be deleted without disturbing the other children.
            Int32 sourceIndex = 0;
            Int32 targetIndex = 0;

            while (targetIndex < this.Children.Count && sourceIndex < childArray.Length)
            {
                // The names of the node (within the scope of their parent node) is unique, just like a file system.  If the list of new children doesn't match up
                // with the list provided by the data model, then we will insert, update or delete the list in order to reconcile the differences.
                AssetNetworkItem    targetItem = this.Children[targetIndex] as AssetNetworkItem;
                DataModel.EntityRow childRow   = childArray[sourceIndex];

                // Items no longer in the data model are deleted from the list.  Items that are already in the list have their contents copied (including children)
                // and new items are created (including their children) and added to the list.
                switch (String.Compare(targetItem.Name, childRow.Name, true, CultureInfo.CurrentCulture))
                {
                case -1:

                    // Remove children no longer in the data model.
                    AssetNetworkItem removedItem = this.Children[targetIndex] as AssetNetworkItem;
                    owner.RemoveDescendant(removedItem);
                    this.Children.Remove(removedItem);
                    break;

                case 0:

                    // Copy items that are already in the list.
                    targetItem.Copy(owner, childRow);
                    sourceIndex++;
                    targetIndex++;
                    break;

                case 1:

                    // Add new items (and their children) when they aren't in the list.
                    AssetNetworkItem addedItem = new AssetNetworkItem(owner, childRow);
                    owner.AddDescendant(addedItem);
                    this.Children.Insert(targetIndex, addedItem);
                    targetIndex++;
                    sourceIndex++;
                    break;
                }
            }

            // This covers the case where there were several children added to the list after the last item in the existing list.  In this situation there is
            // nothing left to reconcile, just a bunch of new items to be concatenated to the current list of children.
            while (sourceIndex < childArray.Length)
            {
                AssetNetworkItem assetNetworkItem = new AssetNetworkItem(owner, childArray[sourceIndex]);
                owner.AddDescendant(assetNetworkItem);
                this.Children.Add(assetNetworkItem);
                sourceIndex++;
            }

            // The next part of the copy operation involves recursing into the children and copying, moving or removing the items to reflect the current hierarchy.
            // This query is the meat of recursing into the hierarchy.  This will create a sorted array of all the child items of this node.
            var leaves = from entityTreeItem in entityRow.GetEntityTreeRowsByFK_Entity_EntityTree_ParentId()
                         where entityTreeItem.EntityRowByFK_Entity_EntityTree_ChildId.IsContainer == false
                         orderby entityTreeItem.EntityRowByFK_Entity_EntityTree_ChildId.Name
                         select entityTreeItem.EntityRowByFK_Entity_EntityTree_ChildId;

            DataModel.EntityRow[] leavesArray = leaves.ToArray <DataModel.EntityRow>();

            // This list is part of a MVVM.  Since this collection is designed to be bound to the user interface, it is important not to wipe it clean and rebuild
            // it each time something changes.  The main idea of this algorithm is to find out what leaves are new, what leaves need to be updated and what
            // leaves need to be deleted without disturbing the other leaves.
            Int32 sourceLeavesIndex = 0;
            Int32 targetLeavesIndex = 0;

            while (targetLeavesIndex < this.Leaves.Count && sourceLeavesIndex < leavesArray.Length)
            {
                // The names of the node (within the scope of their parent node) is unique, just like a file system.  If the list of new leaves doesn't match up
                // with the list provided by the data model, then we will insert, update or delete the list in order to reconcile the differences.
                AssetNetworkItem    targetItem = this.Leaves[targetLeavesIndex] as AssetNetworkItem;
                DataModel.EntityRow leafRow    = leavesArray[sourceLeavesIndex];

                // Items no longer in the data model are deleted from the list.  Items that are already in the list have their contents copied (including leaves)
                // and new items are created (including their leaves) and added to the list.
                switch (String.Compare(targetItem.Name, leafRow.Name, true, CultureInfo.CurrentCulture))
                {
                case -1:

                    // Remove leaves no longer in the data model.
                    AssetNetworkItem removedItem = this.Leaves[targetLeavesIndex] as AssetNetworkItem;
                    owner.RemoveDescendant(removedItem);
                    this.Leaves.Remove(removedItem);
                    break;

                case 0:

                    // Copy items that are already in the list.
                    targetItem.Copy(owner, leafRow);
                    sourceLeavesIndex++;
                    targetLeavesIndex++;
                    break;

                case 1:

                    // Add new items (and their leaves) when they aren't in the list.
                    AssetNetworkItem addedItem = new AssetNetworkItem(owner, leafRow);
                    owner.AddDescendant(addedItem);
                    this.Leaves.Insert(targetLeavesIndex, addedItem);
                    targetLeavesIndex++;
                    sourceLeavesIndex++;
                    break;
                }
            }

            // This covers the case where there were several leaves added to the list after the last item in the existing list.  In this situation there is
            // nothing left to reconcile, just a bunch of new items to be concatenated to the current list of leaves.
            while (sourceLeavesIndex < leavesArray.Length)
            {
                AssetNetworkItem assetNetworkItem = new AssetNetworkItem(owner, leavesArray[sourceLeavesIndex]);
                owner.AddDescendant(assetNetworkItem);
                this.Leaves.Add(assetNetworkItem);
                sourceLeavesIndex++;
            }
        }
        /// <summary>
        /// Creates a data model entry for the given path element.
        /// </summary>
        /// <param name="parentItem"></param>
        /// <param name="path">The full file name of the document to be loaded.</param>
        /// <param name="externalDocument">The properties used to build the document.</param>
        static void CreateDocument(AssetNetworkItem parentItem, String path, DocumentProperty externalDocument)
        {
            // The general idea here is to create the data structure that will be passed to the server to create the documents.  Once built, the same data is used
            // to modify the client side so it will look like the file was imported instantaneously. If the operation to add the document fails, then we will undo
            // the transaction but most of the time it will just look zippin' quick.  This is list of metadata properties used to create the document.
            List <PropertyStoreInfo> propertyStoreList = new List <PropertyStoreInfo>();

            // Each document requires an image so we can display an icon on the directory page.  The icon used for the documents is acquired from the type
            // information (each type is associated with an icon).
            DataModel.TypeRow typeRow = DataModel.Type.TypeKey.Find(externalDocument.typeId);

            // IMPORTANT CONCEPT: In order to create the document on the client side, we need to have unique identifiers.  These unique identifiers will be passed
            // to the server when it is time to create the document and all the records that go with it (metadata, entity tree, etc.).  If the operation is not
            // successful, then we roll back this transaction and all these records will go away, but if the operation is successful, then we'll get back the
            // actual database record which will, by virtue of having the same identifiers created here, update these records with the actual server information
            // (most importantly, we'll get back a RowVersion and all the other server-side defaults).
            CreateDocumentInfo createDocumentInfo = new CreateDocumentInfo();

            createDocumentInfo.EntityId       = Guid.NewGuid();
            createDocumentInfo.EntityTreeId   = Guid.NewGuid();
            createDocumentInfo.Name           = Path.GetFileName(path);
            createDocumentInfo.ParentEntityId = parentItem.EntityId;
            createDocumentInfo.TypeId         = typeRow.TypeId;

            // If a viewer has been specified for this file type, then create a metadata property for the viewer.
            if (!String.IsNullOrEmpty(externalDocument.ViewerUri))
            {
                PropertyStoreInfo viewerPropertyInfo = new PropertyStoreInfo();
                viewerPropertyInfo.PropertyStoreId = Guid.NewGuid();
                viewerPropertyInfo.PropertyId      = PropertyId.Viewer;
                viewerPropertyInfo.Value           = Encoding.Unicode.GetBytes(externalDocument.ViewerUri);
                propertyStoreList.Add(viewerPropertyInfo);
            }

            // This will create a metadata property for the data that goes with an item.  In this case, the data is the file contents.
            PropertyStoreInfo dataPropertyInfo = new PropertyStoreInfo();

            dataPropertyInfo.PropertyId      = PropertyId.Data;
            dataPropertyInfo.PropertyStoreId = Guid.NewGuid();
            using (FileStream fileStream = new FileStream(path, FileMode.Open, FileAccess.Read))
            {
                MemoryStream memoryStream = new MemoryStream();
                DirectoryPage.Copy(fileStream, memoryStream);
                Byte[] buffer = new Byte[memoryStream.Length];
                Array.Copy(memoryStream.GetBuffer(), buffer, memoryStream.Length);
                dataPropertyInfo.Value = buffer;
            }
            propertyStoreList.Add(dataPropertyInfo);

            // Package the properties up in an array.
            createDocumentInfo.Properties = propertyStoreList.ToArray();

            // At this point, we have enough data to create the document on the server.  We could quit now and just send the message off to the server and wait for
            // the server to update the client with the newly created document.  However, this involves a lag time which can be noticable on public systems.  To
            // make it look like the operation was instantaneous, we're going to create the document on the client side.  If the server accepts the web service
            // call, then all this data will be overwritten by the actual server version.  If the operation fails, all this data will be rolled back.
            DataModel.EntityRow entityRow = DataModel.Entity.NewRow() as DataModel.EntityRow;
            entityRow.EntityId     = createDocumentInfo.EntityId;
            entityRow.CreatedTime  = DateTime.Now;
            entityRow.ImageId      = typeRow.ImageId;
            entityRow.Name         = createDocumentInfo.Name;
            entityRow.ModifiedTime = DateTime.Now;
            entityRow.RowVersion   = 0;
            entityRow.IsContainer  = false;
            entityRow.TypeId       = createDocumentInfo.TypeId;
            DataModel.Entity.Rows.Add(entityRow);

            // This will create the client side version of all the properties associated with this document.
            foreach (PropertyStoreInfo propertyStoreInfo in createDocumentInfo.Properties)
            {
                DataModel.PropertyStoreRow viewerPropertyRow = DataModel.PropertyStore.NewRow() as DataModel.PropertyStoreRow;
                viewerPropertyRow.EntityId        = createDocumentInfo.EntityId;
                viewerPropertyRow.PropertyId      = propertyStoreInfo.PropertyId;
                viewerPropertyRow.PropertyStoreId = propertyStoreInfo.PropertyStoreId;
                viewerPropertyRow.RowVersion      = 0;
                viewerPropertyRow.Value           = propertyStoreInfo.Value;
                DataModel.PropertyStore.Rows.Add(viewerPropertyRow);
            }

            // Finally, we need a relation built between the parent container and this document.
            DataModel.EntityTreeRow entityTreeRow = DataModel.EntityTree.NewRow() as DataModel.EntityTreeRow;
            entityTreeRow.ParentId     = parentItem.EntityId;
            entityTreeRow.ChildId      = createDocumentInfo.EntityId;
            entityTreeRow.EntityTreeId = createDocumentInfo.EntityTreeId;
            entityTreeRow.RowVersion   = 0;
            DataModel.EntityTree.Rows.Add(entityTreeRow);

            // The object will be added to the common data model from a background thread.
            ThreadPool.QueueUserWorkItem(DirectoryPage.CreateItemThread, createDocumentInfo);
        }
 /// <summary>
 /// Add a descendant to the list of items managed by this collection.
 /// </summary>
 /// <param name="assetNetworkItem"></param>
 internal void AddDescendant(AssetNetworkItem assetNetworkItem)
 {
     // This list is used to watch for changes to the collection.
     this.entityItemMap.Add(assetNetworkItem.EntityId, assetNetworkItem);
 }