diff --git a/Caching/FileDb/FileDb.dll b/Caching/FileDb/FileDb.dll new file mode 100644 index 00000000..73f4061b Binary files /dev/null and b/Caching/FileDb/FileDb.dll differ diff --git a/Caching/FileDb/FileDb.xml b/Caching/FileDb/FileDb.xml new file mode 100644 index 00000000..4f7062c5 --- /dev/null +++ b/Caching/FileDb/FileDb.xml @@ -0,0 +1,1870 @@ + + + + FileDb + + + + + Represents an open FileDb database file. None of the FileDb classes are re-entrant - + access to the class objects must be syncronised by the calling application. + + + + + + Constructor for FileDb + + + + + + ToString override - returns the DB filename or a string indicating its a memory DB + + + + + + + Open the indicated database file. + If dbFileName is null an in-memory database will be created. + + The filename of the database file to open. + It can be a fully qualified path or, if no path is specified the current folder will be used. + Indicates whether to open read only or not + + + + + Open the indicated database file for encryption. Encryption is "all or nothing", + meaning all records are either encrypted or not. + If dbFileName is null an in-memory database will be created. + + The filename of the database file to open. + It can be a fully qualified path or, if no path is specified the current folder will be used. + A string value to use as the encryption key + Indicates whether to open read only or not + + + + + Close an open database. + + + + + + Create a new database file. If the file exists, it will be overwritten. + If dbFileName is null an in-memory database will be created. + + The full pathname of the file or null/empty to create a memory DB + Array of Fields for the new database. + + + + + Create a new database file. If the file exists, it will be overwritten. + If dbFileName is null an in-memory database will be created. + + The full pathname of the file or null/empty to create a memory DB + List of Fields for the new database. + + + + + Delete an existing database. + + The pathname of the file to delete. + TODO: make static + + + + Indicates if the DB filename's existence + + + True if the file exists, false otherwise + + + + + Start a transaction - a backup of the whole database file is made until the transaction is completed. + Be sure to call either CommitTrans or RollbackTrans so the backup can be disposed + + + + + + Commit the changes since the transaction was begun + + + + + + Roll back the changes since the transaction was begun + + + + + + Add a new record to the database using the name-value pairs in the FieldValues object. + Note that not all fields must be represented. Missing fields will be set to default + values (0, empty or null). Note that only Array datatypes can NULL. + + The name-value pairs to add. + The volatile index of the newly added record. + + + + + Return a Table of Records filtered by the filter parameter. + + A FilterExpression representing the desired filter. + A new Table with the requested Records + + + + + Return a Table of Records filtered by the filter parameter. Only the specified Fields + will be in the Table. + + A FilterExpression representing the desired filter. + The desired fields to be in the returned Table + A new Table with the requested Records and Fields + + + + + Return a Table of Records filtered by the filter parameter. Only the specified Fields + will be in the Table. + + A FilterExpression representing the desired filter. + The desired fields to be in the returned Table + A list of one or more fields to order the returned table by, + or null for default order. If an orderByField is prefixed with "!", that field will sorted + in reverse order. + A new Table with the requested Records and Fields ordered by the specified fields. + + + + + Get all records matching the search expression in the indicated order, if any. + + Represents a single search expression, such as ID = 3 + The list of fields to return or null for all fields + If true, an additional Field named "index" will be returned + which is the ordinal index of the Record in the database, which can be used in + GetRecordByIndex and UpdateRecordByIndex. + A list of one or more fields to order the returned table by, + or null for default order. If an orderByField is prefixed with "!", that field will sorted + in reverse order. + A new Table with the requested Records and Fields + + + + + Return a Table of Records filtered by the filter parameter. + + A FilterExpressionGroup representing the desired filter. + A new Table with the requested Records + + + + + Return a Table of Records filtered by the filter parameter. Only the specified Fields + will be in the Table. + + A FilterExpression representing the desired filter. + The desired fields to be in the returned Table + A new Table with the requested Records and Fields + + + + + Return a Table of Records filtered by the filter parameter. Only the specified Fields + will be in the Table. + + A FilterExpression representing the desired filter. + The desired fields to be in the returned Table + A list of one or more fields to order the returned table by, + or null for default order. If an orderByField is prefixed with "!", that field will sorted + in reverse order. + A new Table with the requested Records and Fields in the specified order + + + + + Get all records matching the FilterExpressionGroup in the indicated order, if any. + + Represents a compound search expression, such as FirstName = "John" AND LastName = "Smith" + The list of fields to return or null for all fields + Specify whether to include the record index as one of the Fields + A list of one or more fields to order the returned table by, + or null for default order. If an orderByField is prefixed with "!", that field will sorted + in reverse order. + A new Table with the requested Records and Fields + + + + + Return a Table of Records filtered by the filter parameter. + + A string representing the desired filter, eg. LastName = 'Fuller' + A new Table with the requested Records + + + + + Return a Table of Records filtered by the filter parameter. Only the specified Fields + will be in the Table. + + A string representing the desired filter, eg. LastName = 'Fuller' + The desired fields to be in the returned Table + A new Table with the requested Records and Fields + + + + + Return a Table of Records filtered by the filter parameter. Only the specified Fields + will be in the Table. + + A string representing the desired filter, eg. LastName = 'Fuller' + The desired fields to be in the returned Table + A list of one or more fields to order the returned table by, + or null for default order. If an orderByField is prefixed with "!", that field will sorted + in reverse order. + A new Table with the requested Records and Fields ordered by the specified list + + + + + Return a Table of Records filtered by the filter parameter. + + A string representing the desired filter, eg. LastName = 'Fuller' + The desired fields to be in the returned Table + If true, an additional Field named "index" will be returned + which is the ordinal index of the Record in the database, which can be used in + GetRecordByIndex and UpdateRecordByIndex + A list of one or more fields to order the returned table by, + or null for default order. If an orderByField is prefixed with "!", that field will sorted + in reverse order + A new Table with the requested Records and Fields + + + + + Return all records in the database (table). + + A table containing all Records and Fields. + + + + + Return all records in the database (table). + + The list of Fields to return or null for all Fields + A table containing all rows. + + + + + Return all records in the database (table). + + The list of fields to return or null for all Fields + A list of one or more fields to order the returned table by, + or null for default order + A table containing all rows. + + + + + Return all records in the database (table). + + Specify whether to include the Record index as one of the Fields + A table containing all rows. + + + + + Return all records in the database (table). + + The list of fields to return or null for all fields + Specify whether to include the record index as one of the Fields + A list of one or more fields to order the returned table by, + or null for default order + A table containing all Records and the specified Fields. + + + + + Sometimes you may need to get an empty table just for the field definitions. + Use this method because its much more efficient than using a contrived filter + which is designed to return no results. + + An empty table containing all fields + + + + + Returns a single Record object at the current location. Meant to be used ONLY in conjunction + with the MoveFirst/MoveNext methods. + + The list of fields to return or null for all fields + Specify whether to include the record index as one of the Fields + A Record object or null + + + + + + Returns a single Record object specified by the index. + + The index of the record to return. This value can be obtained from + Record returning queries by specifying true for the includeIndex parameter. + The list of fields to return or null for all fields + A Record object or null + + + + + Returns a single Record object specified by the primary key value or record number. + + The primary key value. For databases without a primary key, + 'key' is the zero-based record number in the table. + The list of fields to return or null for all fields + Specify whether to include the record index as one of the Fields + A Record object or null + + + + + Update the record at the indicated index. To get the index, you would need to first + get a record from the database then use the index field from it. The index is only + valid until a database operation which would invalidate it, such as adding/deleting + a record, or changing the value of a primary key. + + The record values to update + The index of the record to update + + + + + Update the record with the indicated primary key value. + + The record values to update + The primary key value of the record to update + + + + + Update all records which match the search criteria using the values in record. + + The search expression, e.g. ID = 100 + A list of name-value pairs to use to update the matching records + The number of records which were updated. + + + + + Update all records which match the compound search criteria using the values in record. + + The compound search expression, e.g. FirstName = "John" AND LastName = "Smith" + A list of name-value pairs to use to update the matching records + The number of records which were updated. + + + + + Update all records which match the filter expression using the values in record. + + The filter to use, eg. "~LastName = 'peacock' OR ~FirstName = 'nancy'". + This filter string will be parsed using FilterExpressionGroup.Parse. + A list of name-value pairs to use to update the matching records + The number of records which were updated. + + + + + Delete all records in the database + + The number of records deleted + + + + + Delete the record at the specified index. You would normally get the index from a previous + query. This index is only valid until a record has been deleted. + + The zero-based index of the record to delete. + true if the record was deleted, false otherwise + + + + + Delete the record with the specified primary key value + + The primary key value of the record to delete + true if the record was deleted, false otherwise + + + + + Delete all records which match the search criteria. + + The search expression, e.g. ID = 100 + The number of records deleted + + + + + Delete all records which match the compound search criteria. + + The compound search expression, e.g. FirstName = "John" AND LastName = "Smith" + The number of records deleted + + + + + Delete all records which match the filter criteria. + + The filter to use, eg. "~LastName = 'peacock' OR ~FirstName = 'nancy'". + This filter string will be parsed using FilterExpressionGroup.Parse. + The number of records deleted + + + + + Move to the first record in the index. Use this in conjunction with MoveNext and GetCurrentRecord + + + + + + Move to the next record in the index. Use this in conjunction with MoveFirst and GetCurrentRecord + + + + + Call this to remove deleted records from the file (compact). + + + + + + Call this to write the index and flush the stream buffer to disk. + Flushing will be done automatically if AutoFlush is On (and only writes the index + if necessary), whereas this call always writes the index. + You can use this to periodically write everything to disk rather than each time + as with AutoFlush. Flush is always called when the file is closed, however in that + case the index is only written if AutoFlush is set to Off. + + + + + + Call this method to reindex the database if your index file should be deleted or corrupted. + + + + + + Add the specified Field to the database. + + The new Field to add + A default value to use for the values of existing records for the new Field + + + + + Add the specified Field to the database. + + The new Fields to add + Default values to use for the values of existing records for the new Fields. + Can be null but if not then you must provide a value for each field in the Fields array + + + + + Delete the specified Field from the database. + + The name of the Field to delete + + + + + Delete the specified Fields from the database. + + The Fields to delete + + + + + Rename the specified Field. + + The name of the Field to rename + + + + + Allows you to set an encryption key after the database has been opened. You must set + the encryption key before reading or writing to the database. Encryption is "all or nothing", + meaning all records are either encrypted or not. + + A string value to use as the encryption key + + + + + Encrypt a string value. + Not syncronized. + + The key to use for encryption + The value to encrypt + The encrypted value as a string + + + + + Decrypt a string value. + Not syncronized. + + The key to use for decryption + The value to decrypt + The decrypted value as a string + + + + + Return a List of custom objects filtered by the filter parameter. + + A FilterExpression representing the desired filter. + A new List of custom objects with the requested Records + + + + + Return a List of custom objects filtered by the filter parameter. Only the specified Fields + will be in the Table. + + A FilterExpression representing the desired filter. + The desired fields to be in the returned Table + A new List of custom objects with the requested Records and Fields + + + + + Return a List of custom objects filtered by the filter parameter. Only the specified Fields + will be in the Table. + + A FilterExpression representing the desired filter. + The desired fields to be in the returned Table + A list of one or more fields to order the returned table by, + or null for default order. If an orderByField is prefixed with "!", that field will sorted + in reverse order. + A new List of custom objects with the requested Records and Fields ordered by the specified fields. + + + + + Get all records matching the search expression in the indicated order, if any. + + Represents a single search expression, such as ID = 3 + The list of fields to return or null for all fields + If true, an additional Field named "index" will be returned + which is the ordinal index of the Record in the database, which can be used in + GetRecordByIndex and UpdateRecordByIndex. + A list of one or more fields to order the returned table by, + or null for default order. If an orderByField is prefixed with "!", that field will sorted + in reverse order. + A new List of custom objects with the requested Records and Fields + + + + + Return a List of custom objects filtered by the filter parameter. + + A FilterExpressionGroup representing the desired filter. + A new List of custom objects with the requested Records + + + + + Return a List of custom objects filtered by the filter parameter. Only the specified Fields + will be in the Table. + + A FilterExpression representing the desired filter. + The desired fields to be in the returned Table + A new List of custom objects with the requested Records and Fields + + + + + Return a List of custom objects filtered by the filter parameter. Only the specified Fields + will be in the Table. + + A FilterExpression representing the desired filter. + The desired fields to be in the returned Table + A list of one or more fields to order the returned table by, + or null for default order. If an orderByField is prefixed with "!", that field will sorted + in reverse order. + A new List of custom objects with the requested Records and Fields in the specified order + + + + + Get all records matching the FilterExpressionGroup in the indicated order, if any. + + Represents a compound search expression, such as FirstName = "John" AND LastName = "Smith" + The list of fields to return or null for all fields + Specify whether to include the record index as one of the Fields + A list of one or more fields to order the returned table by, + or null for default order. If an orderByField is prefixed with "!", that field will sorted + in reverse order. + A new List of custom objects with the requested Records and Fields + + + + + Return a List of custom objects filtered by the filter parameter. + + A string representing the desired filter, eg. LastName = 'Fuller' + A new List of custom objects with the requested Records + + + + + Return a List of custom objects filtered by the filter parameter. Only the specified Fields + will be in the Table. + + A string representing the desired filter, eg. LastName = 'Fuller' + The desired fields to be in the returned Table + A new List of custom objects with the requested Records and Fields + + + + + Return a List of custom objects filtered by the filter parameter. Only the specified Fields + will be in the Table. + + A string representing the desired filter, eg. LastName = 'Fuller' + The desired fields to be in the returned Table + A list of one or more fields to order the returned table by, + or null for default order. If an orderByField is prefixed with "!", that field will sorted + in reverse order. + A new List of custom objects with the requested Records and Fields ordered by the specified list + + + + + Return a List of custom objects filtered by the filter parameter. + + A string representing the desired filter, eg. LastName = 'Fuller' + The desired fields to be in the returned Table + If true, an additional Field named "index" will be returned + which is the ordinal index of the Record in the database, which can be used in + GetRecordByIndex and UpdateRecordByIndex + A list of one or more fields to order the returned table by, + or null for default order. If an orderByField is prefixed with "!", that field will sorted + in reverse order + A new List of custom objects with the requested Records and Fields + + + + + Return all records in the database (table). + + A table containing all Records and Fields. + + + + + Return all records in the database (table). + + The list of Fields to return or null for all Fields + A table containing all rows. + + + + + Return all records in the database (table). + + The list of fields to return or null for all Fields + A list of one or more fields to order the returned table by, + or null for default order + A table containing all rows. + + + + + Return all records in the database (table). + + Specify whether to include the Record index as one of the Fields + A table containing all rows. + + + + + Return all records in the database (table). + + The list of fields to return or null for all fields + Specify whether to include the record index as one of the Fields + A list of one or more fields to order the returned table by, + or null for default order + A table containing all Records and the specified Fields. + + + + + Returns a single custom object at the current location. Meant to be used ONLY in conjunction + with the MoveFirst/MoveNext methods. + + The list of fields to return or null for all fields + Specify whether to include the record index as one of the Fields + A new object or null + + + + + + Returns a single custom object specified by the index. + + The index of the record to return. This value can be obtained from + Record returning queries by specifying true for the includeIndex parameter. + The list of fields to return or null for all fields + A new object or null + + + + + Returns a single custom object specified by the primary key value or record number. + + The primary key value. For databases without a primary key, + 'key' is the zero-based record number in the table. + The list of fields to return or null for all fields + Specify whether to include the record index as one of the Fields + A new object or null + + + + + Static event fired when a record has been updated. + + + + + Static event fired when a record has been inserted. + + + + + Static event fired when a record has been deleted. + + + + + Fired when a record has been updated. + + + + + + Fired when a record has been inserted. + + + + + + Fired when a record has been deleted. + + + + + + The full filename of the DB file + + + + + + A value which can be used to keep track of the database version for changes + + + + + The fields of the database (table). + + + + + + The number of records in the database (table). Doesn't include deleted records. + + + + + + The number of deleted records which not yet cleaned from the file. Call the + Clean method to remove all deleted records and compact the file. + + + + + + Configures autoclean. When an edit or delete is made, the + record is normally not removed from the data file - only the index. + After repeated edits/deletions, the data file may become very big with + deleted (non-removed) records. A cleanup is normally done with the + cleanup() method. Autoclean will do this automatically, keeping the + number of deleted records to under the threshold value. + To turn off autoclean, set threshold to a negative value. + + + + + + Specifies whether to automatically flush data buffers and write the index after each + operation in which the file was updated. When AutoFlush is not On, the index is not + written until the file is closed or Flush is called. + You can set AutoFlush to Off just before performing a bulk operation to dramatically + increase performance, then set it back On after. When you set it back On after it was + Off, everything is flushed immediately because the assumption is that it was needed, + so you don't need to call Flush in this case. + + Setting AutoFlush On is most useful for when you aren't able to guarantee that you will + be able to call Close before the program closes. This way the file won't become corrupt + in this case. + + + + + + Tests to see if a database is currently open. + + + + + + Allow ability to store meta data in the DB file. MetaData must be one of the supported + DataTypes: String, Byte, Int, UInt, Float, Double, Bool, DateTime and also Byte[] + + + + + + Handler for static DbRecordUpdated event. + + The name of the updated database + The record index + The fields and new values which were updated + + + + + Handler for static DbRecordAdded event. + + The name of the updated database + The record index + + + + Handler for static DbRecordDeleted event. + + The name of the updated database + The record index + + + + Handler for RecordUpdated event. + + The record index + The fields and new values which were updated + + + + + Handler for RecordAdded event. + + The record index + + + + + Handler for RecordDeleted event. + + The record index + + + + + Specifies the data type for database Fields + + + + + + Specifies the type of match for FilterExpressions with String data types + + + + + Specifies the comparison operator to use for FilterExpressions + + + + + Boolean operands to use to join FilterExpressions + + + + + Constructor + + + + + Open the database files + + + + + + + + Flushes the Stream and detaches it, rendering this FileDb closed + + + + + ---------------------------------------------------------------------------------------- + + record must have all fields + + + + + ---------------------------------------------------------------------------------------- + + record must have all fields + + + + + + Configures autoclean. When an edit or delete is made, the + record is normally not removed from the data file - only the index. + After repeated edits/deletions, the data file may become very big with + deleted (non-removed) records. A cleanup is normally done with the + cleanup() method. Autoclean will do this automatically, keeping the + number of deleted records to under the threshold value. + To turn off autoclean, set threshold to a negative value. + + number of deleted records to have at any one time + + + + ---------------------------------------------------------------------------------------- + + Read all records to create new index. + + + + + + Remove all deleted records + + + + + + Removes an entry from the database INDEX only - it appears + deleted, but the actual data is only removed from the file when a + cleanup() is called. + + Int32 or string primary key used to identify record to remove. For + databases without primary keys, it is the record number (zero based) in + the table. + true if a record was removed, false otherwise + + + + + Removes an entry from the database INDEX only - it appears + deleted, but the actual data is only removed from the file when a + cleanup() is called. + + The record number (zero based) in the table to remove + true on success, false otherwise + + + + + Removes entries from the database INDEX only, based on the + result of a regular expression match on a given field - records appear + deleted, but the actual data is only removed from the file when a + cleanup() is called. + + + number of records removed + + + + move to the first index position + + + + + + Move the current index position to the next database item. + + true if advanced to a new item, false if there are none left + + + + + Return the current record in the database. Note that the current iterator pointer is not moved in any way. + + + + + + + retrieves a record based on the specified key + + primary key used to identify record to retrieve. For + databases without primary keys, it is the record number (zero based) in + the table. + + if true, an extra field called 'IFIELD' will + be added to each record returned. It will contain an Int32 that specifies + the original position in the database (zero based) that the record is + positioned. It might be useful when an orderby is used, and a future + operation on a record is required, given it's index in the table. + record if found, or false otherwise + + + + + retrieves a record based on the record number in the table + (zero based) + + zero based record number to retrieve + + + + + + + + Searches the database for an item, and returns true if found, false otherwise. + + rimary key of record to search for, or the record + number (zero based) for databases without a primary key + true if found, false otherwise + + + + + Returns the number of records in the database + + the number of records in the database + + + + + Returns the number of deleted records in the database, that would be removed if cleanup() is called. + + the number of deleted records in the database + + + + + Returns the current database schema in the same form + as that used in the parameter for the create(...) method. + + + + + + + Flush the in-memory buffers to disk + + + + + + Helper + + + + + + + + Helper + + + + + + + + Use this version if you will need to add/remove indices + + + + + + function to write the index values. We assume the + database has been locked before calling this function. + + + + + + + + + + + to keep track of null fields in written record + + + + + + Use this version only if all of the fields are present and in the correct order in the array + + + + + + + + + Use this version only if all of the fields are present and in the correct order in the array + + + + + + + Write a single field to the file + + + + + + + + ------------------------------------------------------------------------------ + + Private function to perform a binary search + + file offsets into the .dat file, it must be ordered + by primary key. + the left most index to start searching from + the right most index to start searching from + the search target we're looking for + -[insert pos+1] when not found, or the array index+1 + when found. Note that we don't return the normal position, because we + can't differentiate between -0 and +0. + + + + + Helper + + + + + + + + Private function to read a record from the database + + + + + + size does not include the 4 byte record length, but only the total size of the fields + + + + + + + + + function to read a record KEY from the database. Note + that this function relies on the fact that they key is ALWAYS the first + item in the database record as stored on disk. + + + + + + + + + Reads a data type from a file. Note that arrays can only + consist of other arrays, ints, and strings. + + + + + + + + + Write the database schema and other meta information. + + + + + + + Helper + + + + + + + + Use this class for single field searches. + + + + + + Create a FilterExpression with the indicated values + + The name of the Field to filter on + The Field value to filter on + The Equality operator to use in the value comparison + + + + + Create a FilterExpression with the indicated values + + The name of the Field to filter on + The Field value to filter on + The Equality operator to use in the value comparison + The match type, eg. MatchType.Exact + + + + + Create a FilterExpression with the indicated values + + The name of the Field to filter on + The Field value to filter on + The Equality operator to use in the value comparison + The match type, eg. MatchType.Exact + Operator negation + + + + + Parse the expression string to create a FilterExpressionGroup representing a simple expression. + + The string expression. Example: LastName = 'Fuller' + A new FilterExpression representing the simple expression + + + + + Utility method to transform a filesystem wildcard pattern into a regex pattern + eg. december* or mary? + + + + + + + + Create a FilterExpression of type "IN". This type is a HashSet of the values which will be used + to filter the query. + + The name of the Field which will be used in the FilterExpressions + A Table to use to build the IN FilterExpressions + The name of the Field in the Table which holds the value to be used to build the IN FilterExpressions + A new FilterExpression + + + + + Create a FilterExpression of type "IN". This type is a HashSet of the values which will be used + to filter the query. + + The name of the Field which will be used in the FilterExpressions + A List of custom objects to use to build the IN FilterExpression + The name of the Property of the custom class which holds the value to be + used to build the IN FilterExpression + A new FilterExpression + + + + + Create a FilterExpression of type "IN". This type is a HashSet of the values which will be used + to filter the query. + + The name of the Field which will be used in the FilterExpressions + A List of strings to use to build the IN FilterExpression + A new FilterExpression + + + + + Create a FilterExpression of type "IN". This type is a HashSet of the values which will be used + to filter the query. + + The name of the Field which will be used in the FilterExpressions + A List of strings to use to build the IN FilterExpression + A new FilterExpression + + + + + Use this class to group FilterExpression and FilterExpressionGroup to form compound search expressions. + All expressions in the group will be evaluated by the same boolean And/Or operation. Use multiple + FilterExpressionGroups to form any combination of And/Or logic. + + + + + + Parse the expression string to create a FilterExpressionGroup representing a compound expression. + + The string compound expression. Example: (FirstName ~= 'andrew' OR FirstName ~= 'nancy') AND LastName = 'Fuller' + A new FilterExpressionGroup representing the compound expression + + + + + Summary description for HexEncoding. + + + + + Creates a byte array from the hexadecimal string. Each two characters are combined + to create one byte. First two hexadecimal characters become first byte in returned array. + Non-hexadecimal characters are ignored. + + string to convert to byte array + number of characters in string ignored + byte array, in the same left-to-right order as the hexString + + + + Determines if given string is in proper hexadecimal string format + + + + + + + Returns true is c is a hexadecimal digit (A-F, a-f, 0-9) + + Character to test + true if hex digit, false if not + + + + Converts 1 or 2 character string into equivalant byte value + + 1 or 2 character string + byte + + + + Represents a column of the database (table). + + + + + Use this constructor when creating a new database. The ordinal index of the field will be the + order it was added to the field list, unless a primary key field is specified and wasn't the + first in the list. In this case the primary key field will be moved to the first in the list + so that its always first. + + The name of the field + The data type of the field + + + + + Use this constructor when you need to create a Records list manually rather than + using one retured from a query. In this case, you must set the field ordinal index + for each field, starting at zero. + + The name of the field + The data type of the field + The zero-based ordinal index of the field + + + + + Clone this Field + + A new Field with the same values + + + + The name of the field. + + + + + The type of the field. + + + + + The zero-based ordinal index of the field. + + + + + Indicates if this is the one and only primary key field + + + + + Indicate if this is an Array type field + + + + + Used for auto-increment fields. Set to the number which you want incrementing to begin. + Leave it null if not an auto-increment field. + + + + + Returns true if this is an auto-increment field, false otherwise + + + + + Comment for the field + + + + + User property to associate a value with this Field + + + + + Represents data for a row, a Record consists of name-value pairs. + Used when adding data to the database and by the Record object + to store data returned from queries. + + + + + Represents a single row returned from a query. It will + contain one or more fields of the database (table). The last column may be the index of the row (if requested), + which is the zero-based position of the record in the index. If there is no primary key specified for + the database (table), then the index is the record number in the order in which the row was added to the database. + + + + + + Create a Record object with the indicated Fields and values. If creating a list of Record objects + (for a Records list) be sure to use the same Fields list for each Record. + + List of Field objects + Array of values. Each value will be converted to the Field type if possible. + + + + + Create a Record object with the indicated Fields and values. If creating a list of Record objects + (for a Records list) be sure to use the same Fields list for each Record. + + List of Field objects + Array of values. Each value will be converted to the Field type if possible. + + + + + Tests to see if the indicated field is in this Record + + + true if the field is in this Record + + + + + Return the Typed field value. + + The name of the field + The Type field value + + + + + Return the integer field value. + + The name of the field + The integer field value + + + + + Return the integer field value. + + The ordinal index of the field + The integer field value + + + + + Return the unsigned integer field value. + + The name of the field + The unsigned integer field value + + + + + Return the unsigned integer field value. + + The ordinal index of the field + The unsigned integer field value + + + + + Return the String field value. + + The name of the field + The String field value + + + + + Return the String field value. + + The ordinal index of the field + The String field value + + + + + Return the Byte field value. + + The name of the field + The Byte field value + + + + + Return the Byte field value. + + The ordinal index of the field + The Byte field value + + + + + Return the Single field value. + + The name of the field + The Single field value + + + + + Return the Single field value. + + The ordinal index of the field + The Single field value + + + + + Return the Double field value. + + The name of the field + The Double field value + + + + + Return the Decimal field value. + + The ordinal index of the field + The Decimal field value + + + + + Return the Decimal field value. + + The name of the field + The Decimal field value + + + + + Return the Double field value. + + The ordinal index of the field + The Double field value + + + + + Return the Boolean field value. + + The name of the field + The Boolean field value + + + + + Return the Boolean field value. + + The ordinal index of the field + The Boolean field value + + + + + Return the DateTime field value. + + The name of the field + The DateTime field value + + + + + Return the DateTime field value. + + The ordinal index of the field + The DateTime field value + + + + + The number of fields in the Record + + + + + + A property which is used for integrating with the binding framework. + + + + + + Used by the Record class for enumerating in foreach contructs + + + + + + A List of Records + + + + + + A list of Fields + + + + + + Represents a data table returned from a query. A table is made up of Fields and Records. + + + + + + Create a table with the indicated Fields. + + The Fields list to use (a copy is made) + + + + + Create a table with the indicated Fields and records. If copyFields is true, a new + Fields list is created and a copy of each field is made and its ordinal adjusted. + Otherwise the original Fields object is adopted. You should pass false for copyFields + only if you created the Fields list and its Field objects yourself. + + The Fields list to use + Indicates whether to make a copy of the Fields object and each Field. + + + + + Create a table with the indicated Fields and records. If copyFields is true, a new + Fields list is created and a copy of each field is made and its ordinal adjusted. + Otherwise the original Fields object is adopted. You should pass false for copyFields + only if you created the Fields list and its Field objects yourself. + + The Fields list to use + The record data + Indicates whether to make a copy of the Fields object and each Field. + + + + + Create a table with the indicated Fields and records. If copyFields is true, a new + Fields list is created and a copy of each field is made and its ordinal adjusted. + Otherwise the original Fields object is adopted. You should pass false for copyFields + only if you created the Fields list and its Field objects yourself and the Fields in + the Record objects match the data in the Record. + + The Fields list to use + The record data + Indicates whether to make a copy of the Fields object and each Field. + + + + + Add a new Record to this Table with all null values. + + + + + + + Add a new Record to this Table with the specfied values, which must be in the order + of their corresponding fields. + + + + + + + Add a new Record to this Table with the FieldValues + + + + + + + + Save this Table to the indicated file as a new database. If the file exists + it will be overwritten. The new database will be just as if you had created + it from scratch and populated it with the Table data. + + The full path and filename of the new database + + + + + Return a Table of Records filtered by the filter parameter. + + A string representing the desired filter, eg. LastName = 'Fuller' + A new Table with the requested Records + + + + + Return a Table of Records filtered by the filter parameter. Only the specified Fields + will be in the Table. + + A string representing the desired filter, eg. LastName = 'Fuller' + The desired fields to be in the returned Table + A new Table with the requested Records and Fields + + + + + Return a Table of Records filtered by the filter parameter. Only the specified Fields + will be in the Table. + + A string representing the desired filter, eg. LastName = 'Fuller' + The desired fields to be in the returned Table + A list of one or more fields to order the returned table by, + or null for default order. If an orderByField is prefixed with "!", that field will sorted + in reverse order. + A new Table with the requested Records and Fields ordered by the specified list + + + + + Return a Table of Records filtered by the filter parameter. + + A FilterExpression representing the desired filter. + A new Table with the requested Records + + + + + Return a Table of Records filtered by the filter parameter. Only the specified Fields + will be in the Table. + + A FilterExpression representing the desired filter. + The desired fields to be in the returned Table + A new Table with the requested Records and Fields + + + + + Return a Table of Records filtered by the filter parameter. Only the specified Fields + will be in the Table. + + A FilterExpression representing the desired filter. + The desired fields to be in the returned Table + A list of one or more fields to order the returned table by, + or null for default order. If an orderByField is prefixed with "!", that field will sorted + in reverse order. + A new Table with the requested Records and Fields ordered by the specified fields. + + + + + Return a Table of Records filtered by the filter parameter. + + A FilterExpressionGroup representing the desired filter. + A new Table with the requested Records + + + + + Return a Table of Records filtered by the filter parameter. Only the specified Fields + will be in the Table. + + A FilterExpression representing the desired filter. + The desired fields to be in the returned Table + A new Table with the requested Records and Fields + + + + + Return a Table of Records filtered by the filter parameter. Only the specified Fields + will be in the Table. + + A FilterExpression representing the desired filter. + The desired fields to be in the returned Table + A list of one or more fields to order the returned table by, + or null for default order. If an orderByField is prefixed with "!", that field will sorted + in reverse order. + A new Table with the requested Records and Fields in the specified order + + + + + The Fields of the Table + + + + + + A simple class used to communicate property value changes to a Record. + Used by WPF databinding. + + + + + diff --git a/Caching/FileDb/FileDbPcl.dll b/Caching/FileDb/FileDbPcl.dll new file mode 100644 index 00000000..1789b1ce Binary files /dev/null and b/Caching/FileDb/FileDbPcl.dll differ diff --git a/Caching/FileDbCache/FileDb/FileDb.XML b/Caching/FileDb/FileDbPcl.xml similarity index 83% rename from Caching/FileDbCache/FileDb/FileDb.XML rename to Caching/FileDb/FileDbPcl.xml index 47c4a542..3c65bc3d 100644 --- a/Caching/FileDbCache/FileDb/FileDb.XML +++ b/Caching/FileDb/FileDbPcl.xml @@ -1,7 +1,7 @@ - FileDb + FileDbPcl @@ -10,6 +10,495 @@ access to the class objects must be syncronised by the calling application. + + + + Constructor for FileDb + + + + + + ToString override - returns the DB filename or a string indicating its a memory DB + + + + + + + Open with an existing database stream + + The database stream to use - normally a FileStream + + + + + Close an open database. + + + + + + Create a new database using the passed stream, or if null and in-memory DB + + The stream to use or null to create a memory DB + Array of Fields for the new database. + + + + + Create a new database using the passed stream, or if null and in-memory DB + + The stream to use or null to create a memory DB + List of Fields for the new database. + + + + + Start a transaction - a backup of the whole database file is made until the transaction is completed. + Be sure to call either CommitTrans or RollbackTrans so the backup can be disposed + + + + + + Commit the changes since the transaction was begun + + + + + + Roll back the changes since the transaction was begun + + + + + + Add a new record to the database using the name-value pairs in the FieldValues object. + Note that not all fields must be represented. Missing fields will be set to default + values (0, empty or null). Note that only Array datatypes can NULL. + + The name-value pairs to add. + The volatile index of the newly added record. + + + + + Return a Table of Records filtered by the filter parameter. + + A FilterExpression representing the desired filter. + A new Table with the requested Records + + + + + Return a Table of Records filtered by the filter parameter. Only the specified Fields + will be in the Table. + + A FilterExpression representing the desired filter. + The desired fields to be in the returned Table + A new Table with the requested Records and Fields + + + + + Return a Table of Records filtered by the filter parameter. Only the specified Fields + will be in the Table. + + A FilterExpression representing the desired filter. + The desired fields to be in the returned Table + A list of one or more fields to order the returned table by, + or null for default order. If an orderByField is prefixed with "!", that field will sorted + in reverse order. + A new Table with the requested Records and Fields ordered by the specified fields. + + + + + Get all records matching the search expression in the indicated order, if any. + + Represents a single search expression, such as ID = 3 + The list of fields to return or null for all fields + If true, an additional Field named "index" will be returned + which is the ordinal index of the Record in the database, which can be used in + GetRecordByIndex and UpdateRecordByIndex. + A list of one or more fields to order the returned table by, + or null for default order. If an orderByField is prefixed with "!", that field will sorted + in reverse order. + A new Table with the requested Records and Fields + + + + + Return a Table of Records filtered by the filter parameter. + + A FilterExpressionGroup representing the desired filter. + A new Table with the requested Records + + + + + Return a Table of Records filtered by the filter parameter. Only the specified Fields + will be in the Table. + + A FilterExpression representing the desired filter. + The desired fields to be in the returned Table + A new Table with the requested Records and Fields + + + + + Return a Table of Records filtered by the filter parameter. Only the specified Fields + will be in the Table. + + A FilterExpression representing the desired filter. + The desired fields to be in the returned Table + A list of one or more fields to order the returned table by, + or null for default order. If an orderByField is prefixed with "!", that field will sorted + in reverse order. + A new Table with the requested Records and Fields in the specified order + + + + + Get all records matching the FilterExpressionGroup in the indicated order, if any. + + Represents a compound search expression, such as FirstName = "John" AND LastName = "Smith" + The list of fields to return or null for all fields + Specify whether to include the record index as one of the Fields + A list of one or more fields to order the returned table by, + or null for default order. If an orderByField is prefixed with "!", that field will sorted + in reverse order. + A new Table with the requested Records and Fields + + + + + Return a Table of Records filtered by the filter parameter. + + A string representing the desired filter, eg. LastName = 'Fuller' + A new Table with the requested Records + + + + + Return a Table of Records filtered by the filter parameter. Only the specified Fields + will be in the Table. + + A string representing the desired filter, eg. LastName = 'Fuller' + The desired fields to be in the returned Table + A new Table with the requested Records and Fields + + + + + Return a Table of Records filtered by the filter parameter. Only the specified Fields + will be in the Table. + + A string representing the desired filter, eg. LastName = 'Fuller' + The desired fields to be in the returned Table + A list of one or more fields to order the returned table by, + or null for default order. If an orderByField is prefixed with "!", that field will sorted + in reverse order. + A new Table with the requested Records and Fields ordered by the specified list + + + + + Return a Table of Records filtered by the filter parameter. + + A string representing the desired filter, eg. LastName = 'Fuller' + The desired fields to be in the returned Table + If true, an additional Field named "index" will be returned + which is the ordinal index of the Record in the database, which can be used in + GetRecordByIndex and UpdateRecordByIndex + A list of one or more fields to order the returned table by, + or null for default order. If an orderByField is prefixed with "!", that field will sorted + in reverse order + A new Table with the requested Records and Fields + + + + + Return all records in the database (table). + + A table containing all Records and Fields. + + + + + Return all records in the database (table). + + The list of Fields to return or null for all Fields + A table containing all rows. + + + + + Return all records in the database (table). + + The list of fields to return or null for all Fields + A list of one or more fields to order the returned table by, + or null for default order + A table containing all rows. + + + + + Return all records in the database (table). + + Specify whether to include the Record index as one of the Fields + A table containing all rows. + + + + + Return all records in the database (table). + + The list of fields to return or null for all fields + Specify whether to include the record index as one of the Fields + A list of one or more fields to order the returned table by, + or null for default order + A table containing all Records and the specified Fields. + + + + + Sometimes you may need to get an empty table just for the field definitions. + Use this method because its much more efficient than using a contrived filter + which is designed to return no results. + + An empty table containing all fields + + + + + Returns a single Record object at the current location. Meant to be used ONLY in conjunction + with the MoveFirst/MoveNext methods. + + The list of fields to return or null for all fields + Specify whether to include the record index as one of the Fields + A Record object or null + + + + + + Returns a single Record object specified by the index. + + The index of the record to return. This value can be obtained from + Record returning queries by specifying true for the includeIndex parameter. + The list of fields to return or null for all fields + A Record object or null + + + + + Returns a single Record object specified by the primary key value or record number. + + The primary key value. For databases without a primary key, + 'key' is the zero-based record number in the table. + The list of fields to return or null for all fields + Specify whether to include the record index as one of the Fields + A Record object or null + + + + + Update the record at the indicated index. To get the index, you would need to first + get a record from the database then use the index field from it. The index is only + valid until a database operation which would invalidate it, such as adding/deleting + a record, or changing the value of a primary key. + + The record values to update + The index of the record to update + + + + + Update the record with the indicated primary key value. + + The record values to update + The primary key value of the record to update + + + + + Update all records which match the search criteria using the values in record. + + The search expression, e.g. ID = 100 + A list of name-value pairs to use to update the matching records + The number of records which were updated. + + + + + Update all records which match the compound search criteria using the values in record. + + The compound search expression, e.g. FirstName = "John" AND LastName = "Smith" + A list of name-value pairs to use to update the matching records + The number of records which were updated. + + + + + Update all records which match the filter expression using the values in record. + + The filter to use, eg. "~LastName = 'peacock' OR ~FirstName = 'nancy'". + This filter string will be parsed using FilterExpressionGroup.Parse. + A list of name-value pairs to use to update the matching records + The number of records which were updated. + + + + + Delete all records in the database + + The number of records deleted + + + + + Delete the record at the specified index. You would normally get the index from a previous + query. This index is only valid until a record has been deleted. + + The zero-based index of the record to delete. + true if the record was deleted, false otherwise + + + + + Delete the record with the specified primary key value + + The primary key value of the record to delete + true if the record was deleted, false otherwise + + + + + Delete all records which match the search criteria. + + The search expression, e.g. ID = 100 + The number of records deleted + + + + + Delete all records which match the compound search criteria. + + The compound search expression, e.g. FirstName = "John" AND LastName = "Smith" + The number of records deleted + + + + + Delete all records which match the filter criteria. + + The filter to use, eg. "~LastName = 'peacock' OR ~FirstName = 'nancy'". + This filter string will be parsed using FilterExpressionGroup.Parse. + The number of records deleted + + + + + Move to the first record in the index. Use this in conjunction with MoveNext and GetCurrentRecord + + + + + + Move to the next record in the index. Use this in conjunction with MoveFirst and GetCurrentRecord + + + + + Call this to remove deleted records from the file (compact). + + + + + + Call this to write the index and flush the stream buffer to disk. + Flushing will be done automatically if AutoFlush is On (and only writes the index + if necessary), whereas this call always writes the index. + You can use this to periodically write everything to disk rather than each time + as with AutoFlush. Flush is always called when the file is closed, however in that + case the index is only written if AutoFlush is set to Off. + + + + + + Call this method to reindex the database if your index file should be deleted or corrupted. + + + + + + Add the specified Field to the database. + + The new Field to add + A default value to use for the values of existing records for the new Field + + + + + Add the specified Field to the database. + + The new Fields to add + Default values to use for the values of existing records for the new Fields. + Can be null but if not then you must provide a value for each field in the Fields array + + + + + Delete the specified Field from the database. + + The name of the Field to delete + + + + + Delete the specified Fields from the database. + + The Fields to delete + + + + + Rename the specified Field. + + The name of the Field to rename + + + + + Allows you to set an encryption key after the database has been opened. You must set + the encryption key before reading or writing to the database. Encryption is "all or nothing", + meaning all records are either encrypted or not. + + A string value to use as the encryption key + + + + + Encrypt a string value. + Not syncronized. + + The key to use for encryption + The value to encrypt + The encrypted value as a string + + + + + Decrypt a string value. + Not syncronized. + + The key to use for decryption + The value to decrypt + The decrypted value as a string + @@ -223,422 +712,48 @@ A new object or null - + - Open the indicated database file. + Static event fired when a record has been updated. - The filename of the database file to open. It can be a fully qualified - path or, if no path is specified the current folder will be used. - - + - Open the indicated database file for encryption. Encryption is "all or nothing", - meaning all records are either encrypted or not. + Static event fired when a record has been inserted. - The filename of the database file to open. It can be a fully qualified - path or, if no path is specified the current folder will be used. - A string value to use as the encryption key - - + - Close an open database. + Static event fired when a record has been deleted. + + + + + Fired when a record has been updated. - + - Create a new database file. If the file exists, it will be overwritten. - - The full pathname of the file. - Array of Fields for the new database. - - - - - Delete an existing database. - - The pathname of the file to delete. - - - - - Add a new record to the database using the name-value pairs in the FieldValues object. - Note that not all fields must be represented. Missing fields will be set to default - values (0, empty or null). Note that only Array datatypes can NULL. - - The name-value pairs to add. - The volatile index of the newly added record. - - - - - Return a Table of Records filtered by the filter parameter. - - A FilterExpression representing the desired filter. - A new Table with the requested Records - - - - - Return a Table of Records filtered by the filter parameter. Only the specified Fields - will be in the Table. - - A FilterExpression representing the desired filter. - The desired fields to be in the returned Table - A new Table with the requested Records and Fields - - - - - Return a Table of Records filtered by the filter parameter. Only the specified Fields - will be in the Table. - - A FilterExpression representing the desired filter. - The desired fields to be in the returned Table - A list of one or more fields to order the returned table by, - or null for default order. If an orderByField is prefixed with "!", that field will sorted - in reverse order. - A new Table with the requested Records and Fields ordered by the specified fields. - - - - - Get all records matching the search expression in the indicated order, if any. - - Represents a single search expression, such as ID = 3 - The list of fields to return or null for all fields - If true, an additional Field named "index" will be returned - which is the ordinal index of the Record in the database, which can be used in - GetRecordByIndex and UpdateRecordByIndex. - A list of one or more fields to order the returned table by, - or null for default order. If an orderByField is prefixed with "!", that field will sorted - in reverse order. - A new Table with the requested Records and Fields - - - - - Return a Table of Records filtered by the filter parameter. - - A FilterExpressionGroup representing the desired filter. - A new Table with the requested Records - - - - - Return a Table of Records filtered by the filter parameter. Only the specified Fields - will be in the Table. - - A FilterExpression representing the desired filter. - The desired fields to be in the returned Table - A new Table with the requested Records and Fields - - - - - Return a Table of Records filtered by the filter parameter. Only the specified Fields - will be in the Table. - - A FilterExpression representing the desired filter. - The desired fields to be in the returned Table - A list of one or more fields to order the returned table by, - or null for default order. If an orderByField is prefixed with "!", that field will sorted - in reverse order. - A new Table with the requested Records and Fields in the specified order - - - - - Get all records matching the FilterExpressionGroup in the indicated order, if any. - - Represents a compound search expression, such as FirstName = "John" AND LastName = "Smith" - The list of fields to return or null for all fields - Specify whether to include the record index as one of the Fields - A list of one or more fields to order the returned table by, - or null for default order. If an orderByField is prefixed with "!", that field will sorted - in reverse order. - A new Table with the requested Records and Fields - - - - - Return a Table of Records filtered by the filter parameter. - - A string representing the desired filter, eg. LastName = 'Fuller' - A new Table with the requested Records - - - - - Return a Table of Records filtered by the filter parameter. Only the specified Fields - will be in the Table. - - A string representing the desired filter, eg. LastName = 'Fuller' - The desired fields to be in the returned Table - A new Table with the requested Records and Fields - - - - - Return a Table of Records filtered by the filter parameter. Only the specified Fields - will be in the Table. - - A string representing the desired filter, eg. LastName = 'Fuller' - The desired fields to be in the returned Table - A list of one or more fields to order the returned table by, - or null for default order. If an orderByField is prefixed with "!", that field will sorted - in reverse order. - A new Table with the requested Records and Fields ordered by the specified list - - - - - Return a Table of Records filtered by the filter parameter. - - A string representing the desired filter, eg. LastName = 'Fuller' - The desired fields to be in the returned Table - If true, an additional Field named "index" will be returned - which is the ordinal index of the Record in the database, which can be used in - GetRecordByIndex and UpdateRecordByIndex - A list of one or more fields to order the returned table by, - or null for default order. If an orderByField is prefixed with "!", that field will sorted - in reverse order - A new Table with the requested Records and Fields - - - - - Return all records in the database (table). - - A table containing all Records and Fields. - - - - - Return all records in the database (table). - - The list of Fields to return or null for all Fields - A table containing all rows. - - - - - Return all records in the database (table). - - The list of fields to return or null for all Fields - A list of one or more fields to order the returned table by, - or null for default order - A table containing all rows. - - - - - Return all records in the database (table). - - Specify whether to include the Record index as one of the Fields - A table containing all rows. - - - - - Return all records in the database (table). - - The list of fields to return or null for all fields - Specify whether to include the record index as one of the Fields - A list of one or more fields to order the returned table by, - or null for default order - A table containing all Records and the specified Fields. - - - - - Returns a single Record object at the current location. Meant to be used ONLY in conjunction - with the MoveFirst/MoveNext methods. - - The list of fields to return or null for all fields - Specify whether to include the record index as one of the Fields - A Record object or null - - - - - - Returns a single Record object specified by the index. - - The index of the record to return. This value can be obtained from - Record returning queries by specifying true for the includeIndex parameter. - The list of fields to return or null for all fields - A Record object or null - - - - - Returns a single Record object specified by the primary key value or record number. - - The primary key value. For databases without a primary key, - 'key' is the zero-based record number in the table. - The list of fields to return or null for all fields - Specify whether to include the record index as one of the Fields - A Record object or null - - - - - Update the record at the indicated index. To get the index, you would need to first - get a record from the database then use the index field from it. The index is only - valid until a database operation which would invalidate it, such as adding/deleting - a record, or changing the value of a primary key. - - The record values to update - The index of the record to update - - - - - Update the record with the indicated primary key value. - - The record values to update - The primary key value of the record to update - - - - - Update all records which match the search criteria using the values in record. - - The search expression, e.g. ID = 100 - A list of name-value pairs to use to update the matching records - The number of records which were updated. - - - - - Update all records which match the compound search criteria using the values in record. - - The compound search expression, e.g. FirstName = "John" AND LastName = "Smith" - A list of name-value pairs to use to update the matching records - The number of records which were updated. - - - - - Update all records which match the filter expression using the values in record. - - The filter to use, eg. "~LastName = 'peacock' OR ~FirstName = 'nancy'". - This filter string will be parsed using FilterExpressionGroup.Parse. - A list of name-value pairs to use to update the matching records - The number of records which were updated. - - - - - Delete the record at the specified index. You would normally get the index from a previous - query. This index is only valid until a record has been deleted. - - The zero-based index of the record to delete. - true if the record was deleted, false otherwise - - - - - Delete the record with the specified primary key value - - The primary key value of the record to delete - true if the record was deleted, false otherwise - - - - - Delete all records which match the search criteria. - - The search expression, e.g. ID = 100 - The number of records deleted - - - - - Delete all records which match the compound search criteria. - - The compound search expression, e.g. FirstName = "John" AND LastName = "Smith" - The number of records deleted - - - - - Delete all records which match the filter criteria. - - The filter to use, eg. "~LastName = 'peacock' OR ~FirstName = 'nancy'". - This filter string will be parsed using FilterExpressionGroup.Parse. - The number of records deleted - - - - - Move to the first record in the index. Use this in conjunction with MoveNext and GetCurrentRecord + Fired when a record has been inserted. - + - Move to the next record in the index. Use this in conjunction with MoveFirst and GetCurrentRecord - - - - - Call this to remove deleted records from the file (compact). + Fired when a record has been deleted. - + - Call this to write the index and flush the stream buffer to disk. - Flushing will be done automatically if AutoFlush is On (and only writes the index - if necessary), whereas this call always writes the index. - You can use this to periodically write everything to disk rather than each time - as with AutoFlush. Flush is always called when the file is closed, however in that - case the index is only written if AutoFlush is set to Off. + The full filename of the DB file - - - - Call this method to reindex the database if your index file should be deleted or corrupted. - - - - - - Allows you to set an encryption key after the database has been opened. You must set - the encryption key before reading or writing to the database. Encryption is "all or nothing", - meaning all records are either encrypted or not. - - A string value to use as the encryption key - - - - - Encrypt a string value. - Not syncronized. - - The key to use for encryption - The value to encrypt - The encrypted value as a string - - - - - Decrypt a string value. - Not syncronized. - - The key to use for decryption - The value to decrypt - The decrypted value as a string - - A string value which can be used to keep track of the database version for changes + A value which can be used to keep track of the database version for changes @@ -700,6 +815,51 @@ DataTypes: String, Byte, Int, UInt, Float, Double, Bool, DateTime and also Byte[] + + + + Handler for static DbRecordUpdated event. + + The name of the updated database + The record index + The fields and new values which were updated + + + + + Handler for static DbRecordAdded event. + + The name of the updated database + The record index + + + + Handler for static DbRecordDeleted event. + + The name of the updated database + The record index + + + + Handler for RecordUpdated event. + + The record index + The fields and new values which were updated + + + + + Handler for RecordAdded event. + + The record index + + + + + Handler for RecordDeleted event. + + The record index + @@ -712,7 +872,7 @@ Specifies the type of match for FilterExpressions with String data types - + Specifies the comparison operator to use for FilterExpressions @@ -722,90 +882,24 @@ Boolean operands to use to join FilterExpressions - - - Use this class for single field searches. - - - - - - Create a FilterExpression with the indicated values - - The name of the Field to filter on - The Field value to filter on - The Equality operator to use in the value comparison - - - - - Create a FilterExpression with the indicated values - - The name of the Field to filter on - The Field value to filter on - The Equality operator to use in the value comparison - The match type, eg. MatchType.Exact - - - - - Parse the expression string to create a FilterExpressionGroup representing a simple expression. - - The string expression. Example: LastName = 'Fuller' - A new FilterExpression representing the simple expression - - - - - Create a FilterExpression of type "IN". This type is a HashSet of the values which will be used - to filter the query. - - The name of the Field which will be used in the FilterExpressions - A Table to use to build the IN FilterExpressions - The name of the Field in the Table which holds the value to be used to build the IN FilterExpressions - A new FilterExpression - - - - - Create a FilterExpression of type "IN". This type is a HashSet of the values which will be used - to filter the query. - - The name of the Field which will be used in the FilterExpressions - A List of custom objects to use to build the IN FilterExpression - The name of the Property of the custom class which holds the value to be - used to build the IN FilterExpression - A new FilterExpression - - - - - Use this class to group FilterExpression and FilterExpressionGroup to form compound search expressions. - All expressions in the group will be evaluated by the same boolean And/Or operation. Use multiple - FilterExpressionGroups to form any combination of And/Or logic. - - - - - - Parse the expression string to create a FilterExpressionGroup representing a compound expression. - - The string compound expression. Example: (FirstName ~= 'andrew' OR FirstName ~= 'nancy') AND LastName = 'Fuller' - A new FilterExpressionGroup representing the compound expression - - Constructor - + Open the database files - + + + + + Flushes the Stream and detaches it, rendering this FileDb closed + + ---------------------------------------------------------------------------------------- @@ -865,7 +959,7 @@ deleted, but the actual data is only removed from the file when a cleanup() is called. - The record number (zero based) in the table to remove + The record number (zero based) in the table to remove true on success, false otherwise @@ -1101,6 +1195,118 @@ + + + + Use this class for single field searches. + + + + + + Create a FilterExpression with the indicated values + + The name of the Field to filter on + The Field value to filter on + The Equality operator to use in the value comparison + + + + + Create a FilterExpression with the indicated values + + The name of the Field to filter on + The Field value to filter on + The Equality operator to use in the value comparison + The match type, eg. MatchType.Exact + + + + + Create a FilterExpression with the indicated values + + The name of the Field to filter on + The Field value to filter on + The Equality operator to use in the value comparison + The match type, eg. MatchType.Exact + Operator negation + + + + + Parse the expression string to create a FilterExpressionGroup representing a simple expression. + + The string expression. Example: LastName = 'Fuller' + A new FilterExpression representing the simple expression + + + + + Utility method to transform a filesystem wildcard pattern into a regex pattern + eg. december* or mary? + + + + + + + + Create a FilterExpression of type "IN". This type is a HashSet of the values which will be used + to filter the query. + + The name of the Field which will be used in the FilterExpressions + A Table to use to build the IN FilterExpressions + The name of the Field in the Table which holds the value to be used to build the IN FilterExpressions + A new FilterExpression + + + + + Create a FilterExpression of type "IN". This type is a HashSet of the values which will be used + to filter the query. + + The name of the Field which will be used in the FilterExpressions + A List of custom objects to use to build the IN FilterExpression + The name of the Property of the custom class which holds the value to be + used to build the IN FilterExpression + A new FilterExpression + + + + + Create a FilterExpression of type "IN". This type is a HashSet of the values which will be used + to filter the query. + + The name of the Field which will be used in the FilterExpressions + A List of strings to use to build the IN FilterExpression + A new FilterExpression + + + + + Create a FilterExpression of type "IN". This type is a HashSet of the values which will be used + to filter the query. + + The name of the Field which will be used in the FilterExpressions + A List of strings to use to build the IN FilterExpression + A new FilterExpression + + + + + Use this class to group FilterExpression and FilterExpressionGroup to form compound search expressions. + All expressions in the group will be evaluated by the same boolean And/Or operation. Use multiple + FilterExpressionGroups to form any combination of And/Or logic. + + + + + + Parse the expression string to create a FilterExpressionGroup representing a compound expression. + + The string compound expression. Example: (FirstName ~= 'andrew' OR FirstName ~= 'nancy') AND LastName = 'Fuller' + A new FilterExpressionGroup representing the compound expression + @@ -1165,6 +1371,12 @@ The zero-based ordinal index of the field + + + Clone this Field + + A new Field with the same values + The name of the field. @@ -1193,7 +1405,7 @@ Used for auto-increment fields. Set to the number which you want incrementing to begin. - Set to -1 if not an auto-increment field. + Leave it null if not an auto-increment field. @@ -1232,8 +1444,17 @@ Create a Record object with the indicated Fields and values. If creating a list of Record objects (for a Records list) be sure to use the same Fields list for each Record. - - + List of Field objects + Array of values. Each value will be converted to the Field type if possible. + + + + + Create a Record object with the indicated Fields and values. If creating a list of Record objects + (for a Records list) be sure to use the same Fields list for each Record. + + List of Field objects + Array of values. Each value will be converted to the Field type if possible. @@ -1244,7 +1465,15 @@ true if the field is in this Record - + + + Return the Typed field value. + + The name of the field + The Type field value + + + Return the integer field value. @@ -1252,7 +1481,7 @@ The integer field value - + Return the integer field value. @@ -1260,7 +1489,7 @@ The integer field value - + Return the unsigned integer field value. @@ -1268,7 +1497,7 @@ The unsigned integer field value - + Return the unsigned integer field value. @@ -1331,6 +1560,22 @@ The name of the field The Double field value + + + + Return the Decimal field value. + + The ordinal index of the field + The Decimal field value + + + + + Return the Decimal field value. + + The name of the field + The Decimal field value + @@ -1407,6 +1652,24 @@ Represents a data table returned from a query. A table is made up of Fields and Records. + + + + Create a table with the indicated Fields. + + The Fields list to use (a copy is made) + + + + + Create a table with the indicated Fields and records. If copyFields is true, a new + Fields list is created and a copy of each field is made and its ordinal adjusted. + Otherwise the original Fields object is adopted. You should pass false for copyFields + only if you created the Fields list and its Field objects yourself. + + The Fields list to use + Indicates whether to make a copy of the Fields object and each Field. + @@ -1433,21 +1696,36 @@ Indicates whether to make a copy of the Fields object and each Field. - + - Create a new Record with all of the fields of the Table. The Record is not - added to the Table. To do this, call Records.Add. + Add a new Record to this Table with all null values. - + - Save this Table to the indicated file as a new database. If the file exists - it will be overwritten. The new database will be just as if you had created - it from scratch. + Add a new Record to this Table with the specfied values, which must be in the order + of their corresponding fields. - The full path and filename of the new database + + + + + + Add a new Record to this Table with the FieldValues + + + + + + + + Save this Table to the Stream as a new database. If the Stream is null + one will be created and it will be a memory DB. The new database will be just as if you had created + it from scratch and populated it with the Table data. + + The full path and filename of the new database diff --git a/Caching/FileDb/Help.html b/Caching/FileDb/Help.html new file mode 100644 index 00000000..c14fb2bd --- /dev/null +++ b/Caching/FileDb/Help.html @@ -0,0 +1,983 @@ + + + + + +FileDb Overview + + + + + +

Overview

+
+

FileDb is a simple database designed as a simple +local database solution for Xamarin Cross-Platform phone (Android, IOS, WinPhone) +or any .NET application.  FileDb is a No-SQL database + meant for use as a local data store for applications.  + Here are some important points about FileDb:

+ +
    +
  • + Stores one table per file, including its index
  • +
  • + Extremely Lightweight - less than 50K
  • +
  • + Supports field types Int, UInt, Bool, String, Byte, + Float, Double and DateTime and also arrays of the same + types
  • +
  • + Index supports a single Primary Key field (optional)
  • +
  • + Compiled versions for Windows Phone + RT/PCL, + .NET
  • +
  • + FileDb is VERY FAST
  • +
  • + FileDb is FREE to use in your applications
  • +
  • + Use with LINQ to Objects to achieve full relational + capability
  • +
+ +

FileDb was specifically designed to use only native + .NET data types so there would no need to translate + between database storage and the CLR data types.  + So you can just as easily read/write a String[] field as + you would an Int field.  Another feature is that a + database file created on any .NET platform will work on + any other.  So you can create a database file on + your Windows machine and it can be used in a Silverlight + or Windows Phone app.

+ +

LINQ + FileDb gives you full + relational database capability

+

Even though FileDb is a "flat-file" database, using + LINQ it becomes fully relational!  LINQ + to Objects allows you to join Tables together just as + you would do in SQL. All of the power of LINQ is + available to you: Joins, Grouping, Sum - the lot.  + (See + the examples below.)

+

FileDb also has a built-in + query filter parser so you can write SQL-like + filter expressions to make filtering data easy, like + this:

+
+

+ + string filter = "FirstName IN ('Cindy', 'John') AND Age > 32"

+
+

Use FileDb in your .NET and mobile + applications where you need a searchable, updatable + local database.

+Use FileDb with Xamarin cross-platform app +development

Most phone apps only require a +simple database.  By purchasing a source code license you can compile +FileDb into your IOS, Android and Windows Phone projects and use the same exact +data layer code for them all.  This is much easier than using Sqlite +wrappers.  With FileDb's built in encryption you can have everything you +need to have a secure data layer.

+

+FileDb Database Overview

+ +

FileDb is a simple database designed for use on any .NET platform such as Windows +Phone and Silverlight, but its also great for any .NET app where simple local +database storage is needed. For example, instead of using XML config files you +could use a FileDb database to store and retrieve application data much more +efficiently. FileDb allows only a single table per database file, so when we +talk about a FileDb database we really mean a single table with an index. The +index is stored in the file with the data, and allows an optional Primary Key.

+

FileDb is NOT a relational database - it is NO-SLQ, +meaning you can't directly issue SQL commands for querying, adding or updating. However, +you CAN use LINQ with +FileDb to get full relational query capabilities.   And FileDb does include an Expression Parser which parses SQL-like filter +expressions, which makes searching, updating and deleting very easy - see below for an example.

+

And FileDb supports using powerful Regular Expressions for +filtering.

+

FileDb supports AES encryption at the record level. This +means the database schema is not encrypted (field names, etc.), but each record +is entirely encrypted. Encryption is "all or nothing", meaning it expects that +either all records are encrypted or all records are not encrypted. You turn +encryption on by passing an encryption key when opening the database.

+ +

FileDb is thread-safe for multithreading environments, so it can be accessed from +multiple threads at the same time without worrying about database corruption.

+

FileDb databases can only be opened by a single +application. Any attempt to open the file when already open will fail.  +This makes sense since its meant for use by a single application at a time +(FileDb is not meant as a multi-user database, such as SQL Server Express).

+

FileDb Classes

+

The main FileDb classes are: FileDb, Table, +Field and Record.

+ + +
  • FileDb: Represents a database file. All database operations are + initiated through this class.
  • + + +
  • Table: Represents a two + dimensional dataset returned from a query. A Table consists of Fields + and Records.
  • + + +
  • Field: Defines the + properties of the table column, such as Name and DataType.
  • + + +
  • Fields: A List of Field + objects.
  • +
  • + + + Record: A list of data objects represents a single row in a Table.  + Implements IEnumerable and the Data property which is used for DataBinding.
  • + + +
  • Records: A List of + Record objects.
  • + + +
  • FieldValues: A simple + Name/Value pair Dictionary. Use this class when adding and updating records.
  • + + +
  • FilterExpression: Used + to filter records for query, update and delete.
  • + + +
  • FilterExpressionGroup: + Used to create compound expressions by grouping FilterExpressions and + FilterExpressionGroups.
  • +
    +

    Database Fields

    +

    Fields (or Columns) can be of several common types: +String, Int, UInt, Bool, Byte, Float, Double and DateTime, or can also be an array of any of +these types.

    +

    Int Fields can be AutoIncrementing, and you can optionally +specify one field to be Primary Key (it must be of type Int or String).

    +

    FileDb doesn't support the notion of NULL fields for the +non-array type. Only array type fields can have NULL values. The non-array field +values will always have a value, either zero or empty.

    +

    FileDb Records

    +

    FileDb supports two methods of data retrieval.  You can say the +"default" way is with the built-in Record and Records classes.  Think of +Record as the .NET DataRow class, and think of Table as a DataTable.  Table +is a list of Records, and a Record holds the actual values. You access Field +values using indexing just as you would a DataRow, like this:

    +
    + + + + + +
    + FileDb employeesDb = new FileDb();
    + employeesDb.Open( Employees.fdb" );
    +
    + Table employees = employeesDb.SelectAllRecords();
    + Record record = + employees[0];
    + int id = (int) record["EmployeeId"];
    + // or
    + id + = (int) record[0];
    +
    +
    +

    To use a Table with LINQ, you do this:

    +
    + + + + + +
    + var recs = from e in + employees
    +           where (string) + e["FirstName"] == "John"
    +           select e;
    +
    +
    +

    Notice we have to cast the record value to a string.  + This is because, just like with the DataRow, Record + values are all type object.

    +

    Records and Custom Objects

    +

    Records are great because they require no additional + programming and they work with LINQ, albeit with some + casting.  But you can use your own custom classes + if you want because FileDb has template (generic) + overloads for each of the SelectRecords methods.  + You only need to create a class with public properties + which match the names of the fields you want to use.  + Here's an example using the Employees table.

    +
    + + + + + +
    + public class Employee
    + {
    +    public int EmployeeID { get; set; }
    +    public string LastName { get; set; }
    +     + public string + FirstName { get; set; }
    +     + public string Title { + get; set; }
    +     + public string + TitleOfCourtesy { get; set; }
    +     + public DateTime + BirthDate { get; set; }
    +     + public DateTime + HireDate { get; set; }
    +     + public string Address + { get; set; }
    +     + public string City { + get; set; }
    +     + public string Region { + get; set; }
    +     + public string + PostalCode { get; set; }
    +     + public string Country + { get; set; }
    +     + public string + HomePhone { get; set; }
    +     + public string + Extension { get; set; }
    +     + public Byte[] Photo { + get; set; }
    +     + public string Notes { + get; set; }
    +     + public int ReportsTo { + get; set; }
    + }
    +
    +
    +

    The templated SelectRecords versions return a IList<T> + where T is your custom type.

    +
    + + + + + +
    + + IList<Employee> + employees = employeesDb.SelectAllRecords<Employee>();
    + Employee employee + = employees[0];
    + int id = Employee.EmployeeId;
    +
    + var emps = from e in + employees
    +           where e.FirstName + == "John"
    +           select e;
    +
    +
    +

    As you can see, this is much cleaner code.  And + its actually more efficient since the Record class has + more overhead because its not as simple.

    +

    Searching and Filtering

    +

    FileDb uses FilterExpressions and + FilterExpressionGroups to filter records in queries and + updates. We use FilterExpressions for simple queries + which consist of a single field comparison (field = + 'value') and we use FilterExpressionGroups for compound + expressions, where multiple expressions and grouping are + required. You can add either FilterExpressions or + FilterExpressionGroups to a FilterExpressionGroup, thus + creating complex expresssions (FileDb processes + FilterExpressionGroups recursively).

    +

    You can either create your own manually in code or + use the built-in Expression Parser to create them for + you. The Expression Parser recognizes standard SQL + comparison operators. You can see it used in the + examples below. It also recognizes REGEX, which + uses Regular Expressions, and CONTAINS which uses + String.Contains. See the section on + Regular Expressions below for more info. Field names + prefixed with ~ specifies no-case comparison (for + strings only).

    +

    Each time you use () around an expression, a new +FilterExpressionGroup will be created. The inner-most expressions are evaluated +first, just as in SQL.

    + +

    Example 1: Create a FilterExpression

    +
    +
    + + + + + +
    + // build an expression manually
    + FilterExpression searchExp = new FilterExpression( "LastName", "Peacock", + Equality.Equal );
    +
    + // build the same expression using the + parser
    + searchExp = FilterExpression.Parse( "LastName = 'Peacock'" );
    + Table table = employeesDb.SelectRecords( + searchExp, + new string[] { "ID", "LastName" } );
    +
    + // Or you can simply pass the string filter + directly - a FilterExpression will be + created in the same way as above
    +
    + table = employeesDb.SelectRecords( "LastName + = 'Peacock'", new string[] { "ID", "LastName" + } );
    +
    + foreach( Record record in table )
    + {
    +    foreach( object value in record )
    +    {
    +         + Debug.WriteLine( value );
    +     + }
    + }
    +
    +
    + +


    +Example 2: Create a FilterExpressionGroup

    +
    +

    This example creates two identical FilterExpressionGroups, +one using the Expression Parser and the other with code.

    +
    + + + + + +
    // For string fields there are + 2 ways to specify no-case + comparisons: you can prefix fieldnames with ~ or you can use ~= as + demonstrated below
    + // The first form is needed when using the IN operator, eg.
    + FilterExpressionGroup filterExpGrp = + FilterExpressionGroup.Parse( "(FirstName ~= 'andrew' OR ~FirstName = 'nancy') + AND LastName = 'Fuller'" );
    + Table table = employeesDb.SelectRecords( filterExpGrp );
    +
    + // equivalent building it manually
    + var fname1Exp = new FilterExpression( "FirstName", "andrew", Equality.Equal, + MatchType.IgnoreCase );
    + var fname2Exp = new FilterExpression( "FirstName", "nancy", Equality.Equal, + MatchType.IgnoreCase );
    + var lnameExp = new FilterExpression( "LastName", "Fuller", Equality.Equal ); + // this constructor defaults to MatchType.UseCase
    + var fnamesGrp = new FilterExpressionGroup();
    + fnamesGrp.Add( BoolOp.Or, fname1Exp );
    + fnamesGrp.Add( BoolOp.Or, fname2Exp );
    + var allNamesGrp = new FilterExpressionGroup();
    + allNamesGrp.Add( BoolOp.And, lnameExp );
    + allNamesGrp.Add( BoolOp.And, fnamesGrp );
    +
    + table = employeesDb.SelectRecords( allNamesGrp );
    +
    + // or just pass the filter string directly
    +
    + table = employeesDb.SelectRecords( "(FirstName ~= 'andrew' OR ~FirstName = 'nancy') + AND LastName = 'Fuller'" );
    +  
    +
    +
    +


    +FileDb supports these comparison operators:

    + + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    = Equality
    ~= No-case equality - strings only
    <> Not Equal
    != Not Equal (same as <>)
    >= Greater than or Equal
    <= Less than or Equal
    REGEX Use Regular Expression
    CONTAINS Use the .NET String's Contains method
    IN Creates a HashSet of values to use like SQL + IN operator
    NOT Logical Negation, e.g NOT CONTAINS
    +
    + + +
    + +

     

    +
    +

    REGEX - +Regular Expressions in searches and filtering

    +

    FileDb supports using Regular Expressions. You can use any RegEx supported by +.NET. The Expression Parser supports MatchType.RegEx using the REGEX operator.  +Internally, FileDb uses FilterExpressions to evaluate fields.  You don't +need to use them because you can pass in filter strings and they'll be parsed +into FilterExpressions/FilterExpressionGroups for you.  This is just to +show you how can create them manually if you want to.  In +the example below, both FilterExpressionGroups are identical.

    + +
    + + + + + +
    // Using the Expression Parser
    +
    + // You can use brackets around fieldnames if there are spaces in the + name
    + FilterExpressionGroup filterExpGrp = FilterExpressionGroup.Parse( "(~FirstName = 'steven' OR [FirstName] + REGEX 'NANCY') AND LastName = 'Fuller'" );
    + Table table = employeesDb.SelectRecords( filterExpGrp );
    +
    + // we can manually build the same FilterExpressionGroup
    + var fname1Exp = FilterExpression.Parse( "~FirstName = steven" );
    + var fname2Exp = new FilterExpression( "FirstName", "NANCY", Equality.Regex );
    + var lnameExp = new FilterExpression( "LastName", "Fuller", Equality.Equal );
    + var fnamesGrp = new FilterExpressionGroup();
    + fnamesGrp.Add( BoolOp.Or, fname1Exp );
    + fnamesGrp.Add( BoolOp.Or, fname2Exp );
    + var allNamesGrp = new FilterExpressionGroup();
    + allNamesGrp.Add( BoolOp.And, lnameExp );
    + allNamesGrp.Add( BoolOp.And, fnamesGrp );
    +
    + table = employeesDb.SelectRecords( allNamesGrp );
    +
    +
    +
    + +

     

    +
    +

    Using CONTAINS in searches and filtering

    +

    FileDb supports using Regular Expressions. You can use any RegEx supported by +.NET. The Expression Parser supports MatchType.RegEx using the REGEX operator.  +Internally, FileDb uses FilterExpressions to evaluate fields.  You don't +need to use them because you can pass in filter strings and they'll be parsed +into FilterExpressions/FilterExpressionGroups for you.  This is just to +show you how can create them manually if you want to.  In +the example below, both FilterExpressionGroups are identical.

    + +
    + + + + + +
    // You can use brackets around fieldnames if there are spaces in the + name
    + FilterExpressionGroup filterExpGrp = FilterExpressionGroup.Parse( "(~FirstName = 'steven' OR [FirstName] + CONTAINS 'NANC') AND LastName = 'Fuller'" );
    + Table table = employeesDb.SelectRecords( filterExpGrp );
    +
    + // we can manually build the same FilterExpressionGroup
    + var fname1Exp = FilterExpression.Parse( "~FirstName = steven" );
    + var fname2Exp = new FilterExpression( "FirstName", "NANCY", Equality.Contains );
    + var lnameExp = new FilterExpression( "LastName", "Fuller", Equality.Equal );
    + var fnamesGrp = new FilterExpressionGroup();
    + fnamesGrp.Add( BoolOp.Or, fname1Exp );
    + fnamesGrp.Add( BoolOp.Or, fname2Exp );
    + var allNamesGrp = new FilterExpressionGroup();
    + allNamesGrp.Add( BoolOp.And, lnameExp );
    + allNamesGrp.Add( BoolOp.And, fnamesGrp );
    +
    + table = employeesDb.SelectRecords( allNamesGrp );
    +
    +
    +
    +

     

    +

    Sort Ordering

    +

    Query methods allow for sorting the results by fields. To +get a reverse sort, prefix the sort field list with !. To get a no-case sort, +prefix with ~. To get both reverse and no-case sort, use both ! and ~.

    +

    Example:

    +
    + + + + + +
    + + Table table = employeesDb.SelectAllRecords( new string[] { "ID", "Firstname", "LastName", + "Age" }, false, new string[] { "~LastName", "~FirstName", "!Age" } );
    +
    +
    +

    Selecting a Table from a Table

    +

    Another very powerful feature of FileDb is the ability to select a Table from +another Table.  This would allow you to be able to select data from a Table +after the database file has been closed, for example.

    +

    Example:

    +
    + + + + + +
    + + customersDb.Open( path + "Customers.fdb" );
    +
    +// select all fields and records from the database table
    +Table customers = customersDb.SelectAllRecords();
    +
    +Table subCusts = customers.SelectRecords( "CustomerID <> 'ALFKI'",
    +new string[] { "CustomerID", "CompanyName", "City" }, new string[] { "~City", "~CompanyName" +} );
    +
    +
    +

    Encryption

    +

    Using encryption with FileDb is simple. You only need to +specify a string key when you open the database. After that everything is +automatic. The only caveat is you must set a key before you add any records. +Once a single record has been added without a key set you cannot later add +records with a key. Its all or nothing. Likewise, you cannot add records with +encryption and later add records without.  You must set the encryption key +each time you add any records.

    +

    Persisting Tables

    +

    You can easily save a Table as a new database using +Table.SaveToDb.  This method creates a new database file using the Fields +in the Table then populates it using the Records in the Table.  For +example, you can select subsets of your data, save it as a new database and send +it over the Internet.

    +
    + + + + + +
    Table table = employeesDb.SelectAllRecords( new string[] { "ID", "Firstname", "LastName" + } );
    + table.SaveToDb( "Names.fdb" );
    +
    +
    +

    You can also save a Table to a database from the + FileDb Explorer. Just right-click on the Grid to show + the context menu and select the "Create database from + Table..." menu item.

    +

    Using LINQ to Objects with FileDb

    +

    Microsoft has done an amazing job with LINQ.  + They have invested a huge amount of time, effort and $ + in this technology which allows you to query just about + any kind of data in a SQL-like way.  We use LINQ + with FileDb to join Tables as we would using SQL.  + The difference is that instead of doing it all in a + single step with SQL, we must do it in two steps.  + First we select the data Tables from the database files + then we use LINQ to join them together.

    +

    LINQ to Objects produces a list of anonymous types as + its result set.  This is good because we get + strongly typed data objects which we can easily use in WPF/Silverlight apps.

    +

    Here is an example of doing a simple select using + LINQ:

    +
    + + + + + +
    // + Using the IN operator.  Notice the ~ + prefix on the LastName field. This is how +
    + // you can specify case insensitive searches
    +
    + Table + employees = employeesDb.SelectRecords( "~LastName + IN ('Fuller', 'Peacock')" );
    +
    + var query = + from record in employees
    + select new
    + {
    +    ID = record["EmployeeId"],
    +    Name = record["FirstName"] + " " + record["LastName"],
    +    Title = record["Title"]
    + };
    +
    + foreach( var rec in query )
    + {
    +    Debug.WriteLine( rec.ToString() );
    + }
    +
    +

    The only thing LINQ did for us in this example was + gave us a typed list of anonymous objects.  Here's + the same thing but with custom objects:

    + + + + + +
    IList<Employee> employees + = employeesDb.SelectRecords<Employee>( + "~LastName + IN ('Fuller', 'Peacock')" );
    +
    + var query =
    + from e in employees
    + select e;
    +
    + foreach( var emp in query )
    + {
    +    Debug.WriteLine( emp.ToString() );
    + }
    +
    +
    +

    Now lets tap into LINQ's real power to join tables together +like a SQL inner join.  Notice in the following example we use the +FilterExpression.CreateInExpressionFromTable +method.  We do this to get only the records we are going to need with LINQ.  +So using FileDb with LINQ is a two step process.  You first select the +records you will need then use them in the LINQ query.  If your database +files are large, you can filter the records like this.  Otherwise you can +just select all records.

    +
    + + + + + +
    FileDb customersDb = new FileDb(),
    + ordersDb = new FileDb(),
    + orderDetailsDb = new FileDb(),
    + productsDb = new FileDb();
    +
    + customersDb.Open( "Customers.fdb" );
    + ordersDb.Open( "Orders.fdb" );
    + orderDetailsDb.Open( "OrderDetails.fdb" );
    + productsDb.Open( "Products.fdb" );
    +
    + // get our target Customer records
    + // Note that we should select only fields we + need from each table, but to keep the code
    + // simple for this example we just pass in null for the field + list
    +
    + FilterExpression filterExp = + FilterExpression.Parse( "CustomerID IN( 'ALFKI', + 'BONAP' )" );
    + FileDbNs.Table customers = + customersDb.SelectRecords( filterExp );
    +
    + // now get only Order records for the target + Customer records
    + // CreateInExpressionFromTable will create + an IN FilterExpression, which uses a HashSet +
    + // for high efficiency when filtering + records

    + filterExp = + FilterExpression.CreateInExpressionFromTable( + "CustomerID", customers, "CustomerID" );
    + FileDbNs.Table orders = + ordersDb.SelectRecords( filterExp );
    +
    + // now get only OrderDetails records for the + target Order records
    +
    + filterExp = + FilterExpression.CreateInExpressionFromTable( + "OrderID", orders, "OrderID" );
    + FileDbNs.Table orderDetails = + orderDetailsDb.SelectRecords( filterExp );
    +
    + // now get only Product records for the + target OrderDetails records
    +
    + filterExp = + FilterExpression.CreateInExpressionFromTable( + "ProductID", orderDetails, "ProductID" );
    + FileDbNs.Table products = + productsDb.SelectRecords( filterExp );
    +
    + // now we're ready to do the join
    +
    + var query =
    +    from custRec in customers
    +    join orderRec in orders on custRec["CustomerID"] + equals orderRec["CustomerID"]
    +    join orderDetailRec in orderDetails on + orderRec["OrderID"] equals + orderDetailRec["OrderID"]
    +    join productRec in products on + orderDetailRec["ProductID"] equals + productRec["ProductID"]
    +    select new
    +    {
    +        ID = custRec["CustomerID"],
    +        CompanyName = custRec["CompanyName"],
    +        OrderID = orderRec["OrderID"],
    +        OrderDate = orderRec["OrderDate"],
    +        ProductName = productRec["ProductName"],
    +        UnitPrice = orderDetailRec["UnitPrice"],
    +        Quantity = orderDetailRec["Quantity"]
    +    };
    +
    + foreach( var rec in query )
    + {
    +    Debug.WriteLine( rec.ToString() );
    + }
    +
    +

    Here's the same thing again using custom objects:

    + + + + + +
    // get our target Customer records
    +
    + FilterExpression filterExp = + FilterExpression.Parse( "CustomerID IN( 'ALFKI', + 'BONAP' )" );
    + IList<Customer> customers = + customersDb.SelectRecords<Customer>( filterExp );

    + filterExp = + FilterExpression.CreateInExpressionFromTable<Customer>( + "CustomerID", customers, "CustomerID" );
    + IList<Order> orders = + ordersDb.SelectRecords<Order>( filterExp );
    +
    + // now get only OrderDetails records for the + target Order records
    +
    + filterExp = + FilterExpression.CreateInExpressionFromTable<Order>( + "OrderID", orders, "OrderID" );
    + IList<OrderDetail> orderDetails = + orderDetailsDb.SelectRecords<OrderDetail>( filterExp );
    +
    + // now get only Product records for the + target OrderDetails records
    +
    + filterExp = + FilterExpression.CreateInExpressionFromTable<OrderDetail>( + "ProductID", orderDetails, "ProductID" );
    + IList<Product> products = + productsDb.SelectRecords<Product>(( filterExp );
    +
    + // now we're ready to do the join
    +
    + var query =
    +    from custRec in customers
    +    join orderRec in orders on custRec.CustomerID + equals orderRec.CustomerID
    +    join orderDetailRec in orderDetails on + orderRec.OrderID equals orderDetailRec.OrderID
    +    join productRec in products on + orderDetailRec.ProductID equals productRec.ProductID
    +    select new
    +    {
    +        ID = custRec.CustomerID,
    +        CompanyName = custRec.CompanyName,
    +        OrderID = orderRec.OrderID,
    +        OrderDate = orderRec.OrderDate,
    +        ProductName = productRec.ProductName,
    +        UnitPrice = orderDetailRec.UnitPrice,
    +        Quantity = orderDetailRec.Quantity
    +    };
    +
    + foreach( var rec in query )
    + {
    +    Debug.WriteLine( rec.ToString() );
    + }
    +
    +

     

    +
    +

    + Creating a Database

    + +

    You create your database programmatically by defining +Fields and adding them to an array then calling FileDb.Create, similar to below. +Notice we set the ID field to be AutoIncrementing and PrimaryKey. This code +creates a database with every type of field.

    + +
    + + + + +
    Field field;
    + var fieldLst = new List<Field>( 20 );
    + field = new Field( "ID", DataType.Int );
    + field.AutoIncStart = 0;
    + field.IsPrimaryKey = true;
    + fields.Add( field );
    + field = new Field( "FirstName", DataType.String );
    + fields.Add( field );
    + field = new Field( "LastName", DataType.String );
    + fields.Add( field );
    + field = new Field( "BirthDate", DataType.DateTime );
    + fields.Add( field );
    + field = new Field( "IsCitizen", DataType.Bool );
    + fields.Add( field );
    + field = new Field( "DoubleField", DataType.Double );
    + fields.Add( field );
    + field = new Field( "ByteField", DataType.Byte );
    + fields.Add( field );
    +
    + // array types
    + field = new Field( "StringArrayField", DataType.String );
    + field.IsArray = true;
    + fields.Add( field );
    + field = new Field( "ByteArrayField", DataType.Byte );
    + field.IsArray = true;
    + fields.Add( field );
    + field = new Field( "IntArrayField", DataType.Int );
    + field.IsArray = true;
    + fields.Add( field );
    + field = new Field( "DoubleArrayField", DataType.Double );
    + field.IsArray = true;
    + fields.Add( field );
    + field = new Field( "DateTimeArrayField", DataType.DateTime );
    + field.IsArray = true;
    + fields.Add( field );
    + field = new Field( "BoolArray", DataType.Bool );
    + field.IsArray = true;
    + fields.Add( field );

    +
    + var myDb = new FileDb();

    + myDb.Create( "MyDatabase.fdb", fieldLst.ToArray() );
    +
    +
    +


    +Adding Records

    +

    You add records to a database by creating a FieldValues +object and adding field values. You do not need to represent every field of the +database. Fields that are missing will be initialized to the default value (zero +for numeric types, DateTime.MinValue, +empty for String and NULL for array types).

    + +
    + + + + +
    +var record = new FieldValues();
    + record.Add( "FirstName", "Nancy" );
    + record.Add( "LastName", "Davolio" );
    + record.Add( "BirthDate", new DateTime( 1968, 12, 8 ) );
    + record.Add( "IsCitizen", true );
    + record.Add( "Double", 1.23 );
    + record.Add( "Byte", 1 );
    + record.Add( "StringArray", new string[] { "s1", "s2", "s3" } );
    + record.Add( "ByteArray", new Byte[] { 1, 2, 3, 4 } );
    + record.Add( "IntArray", new int[] { 100, 200, 300, 400 } );
    + record.Add( "DoubleArray", new double[] { 1.2, 2.4, 3.6, 4.8 } );
    + record.Add( "DateTimeArray", new DateTime[] { DateTime.Now, DateTime.Now, + DateTime.Now, DateTime.Now } );
    + record.Add( "BoolArray", new bool[] { true, false, true, false } );
    +
    + myDb.AddRecord( record );
    +
    +

     

    +
    + + + + \ No newline at end of file diff --git a/Caching/FileDbCache/FileDbCache.csproj b/Caching/FileDbCache.WPF/FileDbCache.WPF.csproj similarity index 85% rename from Caching/FileDbCache/FileDbCache.csproj rename to Caching/FileDbCache.WPF/FileDbCache.WPF.csproj index 8f4d183d..ab6c3a81 100644 --- a/Caching/FileDbCache/FileDbCache.csproj +++ b/Caching/FileDbCache.WPF/FileDbCache.WPF.csproj @@ -8,8 +8,8 @@ {EF44F661-B98A-4676-927F-85D138F82300} Library Properties - Caching - FileDbCache + MapControl.Caching + FileDbCache.WPF v4.5 512 @@ -40,27 +40,27 @@ ..\..\MapControl.snk - + False - FileDb\FileDb.dll + ..\FileDb\FileDb.dll + + + - - - - MapControl.snk + + \ No newline at end of file diff --git a/Caching/FileDbCache.WinRT/FileDbCache.cs b/Caching/FileDbCache.WinRT/FileDbCache.cs new file mode 100644 index 00000000..ba288ca9 --- /dev/null +++ b/Caching/FileDbCache.WinRT/FileDbCache.cs @@ -0,0 +1,278 @@ +// XAML Map Control - http://xamlmapcontrol.codeplex.com/ +// Copyright © 2014 Clemens Fischer +// Licensed under the Microsoft Public License (Ms-PL) + +using System; +using System.Diagnostics; +using System.IO; +using System.Runtime.InteropServices.WindowsRuntime; +using System.Threading.Tasks; +using Windows.Storage; +using Windows.Storage.Streams; +using Windows.UI.Xaml; +using FileDbNs; + +namespace MapControl.Caching +{ + /// + /// IImageCache implementation based on FileDb, a free and simple No-SQL database by EzTools Software. + /// See http://www.eztools-software.com/tools/filedb/. + /// + public class FileDbCache : IImageCache, IDisposable + { + private const string keyField = "Key"; + private const string valueField = "Value"; + private const string expiresField = "Expires"; + + private readonly FileDb fileDb = new FileDb { AutoFlush = true, AutoCleanThreshold = -1 }; + private readonly StorageFolder folder; + private readonly string name; + + public FileDbCache(string name = null, StorageFolder folder = null) + { + if (string.IsNullOrWhiteSpace(name)) + { + name = TileImageLoader.DefaultCacheName; + } + + if (string.IsNullOrEmpty(Path.GetExtension(name))) + { + name += ".fdb"; + } + + if (folder == null) + { + folder = TileImageLoader.DefaultCacheFolder; + } + + this.folder = folder; + this.name = name; + + Application.Current.Resuming += async (s, e) => await Open(); + Application.Current.Suspending += (s, e) => Close(); + + var task = Open(); + } + + public void Dispose() + { + Close(); + } + + public void Clean() + { + if (fileDb.IsOpen) + { + fileDb.DeleteRecords(new FilterExpression(expiresField, DateTime.UtcNow, ComparisonOperatorEnum.LessThan)); + + if (fileDb.NumDeleted > 0) + { + Debug.WriteLine("FileDbCache: Deleted {0} expired items.", fileDb.NumDeleted); + fileDb.Clean(); + } + } + } + + public async Task GetAsync(string key) + { + if (key == null) + { + throw new ArgumentNullException("The parameter key must not be null."); + } + + if (!fileDb.IsOpen) + { + return null; + } + + return await Task.Run(() => Get(key)); + } + + public async Task SetAsync(string key, IBuffer buffer, DateTime expires) + { + if (key == null) + { + throw new ArgumentNullException("The parameter key must not be null."); + } + + if (buffer == null) + { + throw new ArgumentNullException("The parameter buffer must not be null."); + } + + if (fileDb.IsOpen) + { + var bytes = buffer.ToArray(); + var ok = await Task.Run(() => AddOrUpdateRecord(key, bytes, expires)); + + if (!ok && (await RepairDatabase())) + { + await Task.Run(() => AddOrUpdateRecord(key, bytes, expires)); + } + } + } + + private async Task Open() + { + if (!fileDb.IsOpen) + { + try + { + var file = await folder.GetFileAsync(name); + var stream = await file.OpenAsync(FileAccessMode.ReadWrite); + + fileDb.Open(stream.AsStream()); + Debug.WriteLine("FileDbCache: Opened database with {0} cached items in {1}.", fileDb.NumRecords, file.Path); + + Clean(); + return; + } + catch + { + } + + await CreateDatabase(); + } + } + + private void Close() + { + if (fileDb.IsOpen) + { + fileDb.Close(); + } + } + + private async Task CreateDatabase() + { + Close(); + + var file = await folder.CreateFileAsync(name, CreationCollisionOption.ReplaceExisting); + var stream = await file.OpenAsync(FileAccessMode.ReadWrite); + + fileDb.Create(stream.AsStream(), new Field[] + { + new Field(keyField, DataTypeEnum.String) { IsPrimaryKey = true }, + new Field(valueField, DataTypeEnum.Byte) { IsArray = true }, + new Field(expiresField, DataTypeEnum.DateTime) + }); + + Debug.WriteLine("FileDbCache: Created database {0}.", file.Path); + } + + private async Task RepairDatabase() + { + try + { + fileDb.Reindex(); + return true; + } + catch (Exception ex) + { + Debug.WriteLine("FileDbCache: FileDb.Reindex() failed: {0}", ex.Message); + } + + try + { + await CreateDatabase(); + return true; + } + catch (Exception ex) + { + Debug.WriteLine("FileDbCache: Creating database {0} failed: {1}", Path.Combine(folder.Path, name), ex.Message); + } + + return false; + } + + private ImageCacheItem Get(string key) + { + var fields = new string[] { valueField, expiresField }; + Record record = null; + + try + { + record = fileDb.GetRecordByKey(key, fields, false); + } + catch (Exception ex) + { + Debug.WriteLine("FileDbCache: FileDb.GetRecordByKey(\"{0}\") failed: {1}", key, ex.Message); + } + + if (record != null) + { + try + { + return new ImageCacheItem + { + Buffer = ((byte[])record[0]).AsBuffer(), + Expires = (DateTime)record[1] + }; + } + catch (Exception ex) + { + Debug.WriteLine("FileDbCache: Decoding \"{0}\" failed: {1}", key, ex.Message); + } + + try + { + fileDb.DeleteRecordByKey(key); + } + catch (Exception ex) + { + Debug.WriteLine("FileDbCache: FileDb.DeleteRecordByKey(\"{0}\") failed: {1}", key, ex.Message); + } + } + + return null; + } + + private bool AddOrUpdateRecord(string key, byte[] value, DateTime expires) + { + var fieldValues = new FieldValues(3); + fieldValues.Add(valueField, value); + fieldValues.Add(expiresField, expires); + + bool recordExists; + + try + { + recordExists = fileDb.GetRecordByKey(key, new string[0], false) != null; + } + catch (Exception ex) + { + Debug.WriteLine("FileDbCache: FileDb.GetRecordByKey(\"{0}\") failed: {1}", key, ex.Message); + return false; + } + + if (recordExists) + { + try + { + fileDb.UpdateRecordByKey(key, fieldValues); + } + catch (Exception ex) + { + Debug.WriteLine("FileDbCache: FileDb.UpdateRecordByKey(\"{0}\") failed: {1}", key, ex.Message); + return false; + } + } + else + { + try + { + fieldValues.Add(keyField, key); + fileDb.AddRecord(fieldValues); + } + catch (Exception ex) + { + Debug.WriteLine("FileDbCache: FileDb.AddRecord(\"{0}\") failed: {1}", key, ex.Message); + return false; + } + } + + //Debug.WriteLine("Cached item {0}, expires {1}", key, expires); + return true; + } + } +} diff --git a/Caching/FileDbCache.WinRT/Properties/AssemblyInfo.cs b/Caching/FileDbCache.WinRT/Properties/AssemblyInfo.cs new file mode 100644 index 00000000..02eedcb5 --- /dev/null +++ b/Caching/FileDbCache.WinRT/Properties/AssemblyInfo.cs @@ -0,0 +1,14 @@ +using System.Reflection; +using System.Runtime.InteropServices; + +[assembly: AssemblyTitle("XAML Map Control FileDbCache (WinRT)")] +[assembly: AssemblyDescription("IImageCache implementation based on EzTools FileDb")] +[assembly: AssemblyProduct("XAML Map Control")] +[assembly: AssemblyCompany("Clemens Fischer")] +[assembly: AssemblyCopyright("Copyright © 2014 Clemens Fischer")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyVersion("2.4.0")] +[assembly: AssemblyFileVersion("2.4.0")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCulture("")] +[assembly: ComVisible(false)] diff --git a/Caching/FileDbCache/FileDb/FileDb.dll b/Caching/FileDbCache/FileDb/FileDb.dll deleted file mode 100644 index 2022a619..00000000 Binary files a/Caching/FileDbCache/FileDb/FileDb.dll and /dev/null differ diff --git a/Caching/FileDbCache/FileDb/FileDb.txt b/Caching/FileDbCache/FileDb/FileDb.txt deleted file mode 100644 index f3d49597..00000000 --- a/Caching/FileDbCache/FileDb/FileDb.txt +++ /dev/null @@ -1,5 +0,0 @@ -FileDbCache uses FileDb by Brett Goodman (aka EzTools), a simple No-SQL database for .NET. -FileDb is Open Source on Google Code Hosting at http://code.google.com/p/filedb-database/ -and licensed under the Apache License 2.0, http://www.apache.org/licenses/LICENSE-2.0. -See the product homepage at http://www.eztools-software.com/tools/filedb/. -Download FileDb from http://www.eztools-software.com/downloads/filedb.exe. diff --git a/Caching/ImageFileCache/ImageFileCache.csproj b/Caching/ImageFileCache.WPF/ImageFileCache.WPF.csproj similarity index 91% rename from Caching/ImageFileCache/ImageFileCache.csproj rename to Caching/ImageFileCache.WPF/ImageFileCache.WPF.csproj index 75ce1be8..b3514996 100644 --- a/Caching/ImageFileCache/ImageFileCache.csproj +++ b/Caching/ImageFileCache.WPF/ImageFileCache.WPF.csproj @@ -8,8 +8,8 @@ {86470440-FEE2-4120-AF5A-3762FB9C536F} Library Properties - Caching - ImageFileCache + MapControl.Caching + ImageFileCache.WPF v4.5 512 @@ -40,9 +40,12 @@ ..\..\MapControl.snk + + + diff --git a/Caching/ImageFileCache/ImageFileCache.cs b/Caching/ImageFileCache.WPF/ImageFileCache.cs similarity index 53% rename from Caching/ImageFileCache/ImageFileCache.cs rename to Caching/ImageFileCache.WPF/ImageFileCache.cs index d3f0fa5c..bf3bab36 100644 --- a/Caching/ImageFileCache/ImageFileCache.cs +++ b/Caching/ImageFileCache.WPF/ImageFileCache.cs @@ -6,61 +6,52 @@ using System; using System.Collections.Generic; using System.Collections.Specialized; using System.Diagnostics; +using System.Globalization; using System.IO; using System.Linq; using System.Runtime.Caching; using System.Security.AccessControl; using System.Security.Principal; +using System.Windows.Media.Imaging; -namespace Caching +namespace MapControl.Caching { /// /// ObjectCache implementation based on local image files. - /// The only valid data type for cached values is a byte array containing an - /// 8-byte timestamp followed by a PNG, JPEG, BMP, GIF, TIFF or WMP image buffer. + /// The only valid data type for cached values is System.Windows.Media.Imaging.BitmapFrame. /// public class ImageFileCache : ObjectCache { - private static readonly Tuple[] imageFileTypes = new Tuple[] - { - new Tuple(".png", new byte[] { 0x89, 0x50, 0x4E, 0x47, 0xD, 0xA, 0x1A, 0xA }), - new Tuple(".jpg", new byte[] { 0xFF, 0xD8, 0xFF, 0xE0, 0, 0x10, 0x4A, 0x46, 0x49, 0x46, 0 }), - new Tuple(".bmp", new byte[] { 0x42, 0x4D }), - new Tuple(".gif", new byte[] { 0x47, 0x49, 0x46 }), - new Tuple(".tif", new byte[] { 0x49, 0x49, 42, 0 }), - new Tuple(".tif", new byte[] { 0x4D, 0x4D, 0, 42 }), - new Tuple(".wdp", new byte[] { 0x49, 0x49, 0xBC }), - }; - private static readonly FileSystemAccessRule fullControlRule = new FileSystemAccessRule( new SecurityIdentifier(WellKnownSidType.BuiltinUsersSid, null), FileSystemRights.FullControl, AccessControlType.Allow); + private readonly MemoryCache memoryCache = MemoryCache.Default; private readonly string name; - private readonly string directory; + private readonly string rootFolder; public ImageFileCache(string name, NameValueCollection config) - : this(name, config["directory"]) + : this(name, config["folder"]) { } - public ImageFileCache(string name, string directory) + public ImageFileCache(string name, string folder) { if (string.IsNullOrWhiteSpace(name)) { - throw new ArgumentException("The parameter name must not be null or empty or only white-space."); + throw new ArgumentException("The parameter name must not be null or empty or consist only of white-space characters."); } - if (string.IsNullOrWhiteSpace(directory)) + if (string.IsNullOrWhiteSpace(folder)) { - throw new ArgumentException("The parameter directory must not be null or empty or only white-space."); + throw new ArgumentException("The parameter folder must not be null or empty or consist only of white-space characters."); } this.name = name; - this.directory = Path.Combine(directory, name.Trim()); - Directory.CreateDirectory(this.directory); + rootFolder = Path.Combine(folder, name); + Directory.CreateDirectory(rootFolder); - Trace.TraceInformation("Created ImageFileCache in {0}", this.directory); + Debug.WriteLine("Created ImageFileCache in {0}.", (object)rootFolder); } public override string Name @@ -81,46 +72,29 @@ namespace Caching protected override IEnumerator> GetEnumerator() { - throw new NotSupportedException("LocalFileCache does not support the ability to enumerate items."); + throw new NotSupportedException("ImageFileCache does not support the ability to enumerate items."); } public override CacheEntryChangeMonitor CreateCacheEntryChangeMonitor(IEnumerable keys, string regionName = null) { - throw new NotSupportedException("LocalFileCache does not support the ability to create change monitors."); + throw new NotSupportedException("ImageFileCache does not support the ability to create change monitors."); } public override long GetCount(string regionName = null) { - throw new NotSupportedException("LocalFileCache does not support the ability to count items."); + throw new NotSupportedException("ImageFileCache does not support the ability to count items."); } public override bool Contains(string key, string regionName = null) { - if (regionName != null) - { - throw new NotSupportedException("The parameter regionName must be null."); - } - - try - { - return MemoryCache.Default.Contains(key) || FindFile(GetPath(key)) != null; - } - catch - { - return false; - } + return memoryCache.Contains(key, regionName) || FindFile(GetPath(key)) != null; } public override object Get(string key, string regionName = null) { - if (regionName != null) - { - throw new NotSupportedException("The parameter regionName must be null."); - } + var bitmap = memoryCache.Get(key, regionName) as BitmapFrame; - var value = MemoryCache.Default.Get(key); - - if (value == null) + if (bitmap == null) { try { @@ -128,20 +102,20 @@ namespace Caching if (path != null) { - var expirationTime = File.GetLastAccessTimeUtc(path); - var creationTime = File.GetLastWriteTimeUtc(path); - - if (expirationTime < creationTime) + using (var fileStream = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read)) { - expirationTime = creationTime; - } + bitmap = BitmapFrame.Create(fileStream, BitmapCreateOptions.None, BitmapCacheOption.OnLoad); - using (var fileStream = new FileStream(path, FileMode.Open)) - using (var memoryStream = new MemoryStream((int)(fileStream.Length + 8))) - { - memoryStream.Write(BitConverter.GetBytes(expirationTime.ToBinary()), 0, 8); - fileStream.CopyTo(memoryStream); - value = memoryStream.GetBuffer(); + var metadata = (BitmapMetadata)bitmap.Metadata; + DateTime expiration; + + // metadata.DateTaken must be parsed in CurrentCulture + if (metadata != null && + metadata.DateTaken != null && + DateTime.TryParse(metadata.DateTaken, CultureInfo.CurrentCulture, DateTimeStyles.AdjustToUniversal | DateTimeStyles.AssumeUniversal, out expiration)) + { + memoryCache.Set(key, bitmap, expiration, regionName); + } } } } @@ -150,7 +124,7 @@ namespace Caching } } - return value; + return bitmap; } public override CacheItem GetCacheItem(string key, string regionName = null) @@ -162,58 +136,72 @@ namespace Caching public override IDictionary GetValues(IEnumerable keys, string regionName = null) { - if (regionName != null) - { - throw new NotSupportedException("The parameter regionName must be null."); - } - - var values = new Dictionary(); - - foreach (string key in keys) - { - values[key] = Get(key); - } - - return values; + return keys.ToDictionary(key => key, key => Get(key, regionName)); } public override void Set(string key, object value, CacheItemPolicy policy, string regionName = null) { - if (regionName != null) + var bitmap = value as BitmapFrame; + + if (bitmap == null) { - throw new NotSupportedException("The parameter regionName must be null."); + throw new ArgumentException("The parameter value must contain a System.Windows.Media.Imaging.BitmapFrame."); } - var buffer = value as byte[]; + var metadata = (BitmapMetadata)bitmap.Metadata; + var format = metadata != null ? metadata.Format : "bmp"; + BitmapEncoder encoder = null; - if (buffer == null || buffer.Length <= 8) + switch (format) { - throw new NotSupportedException("The parameter value must be a byte[] containing at least 9 bytes."); + case "bmp": + encoder = new BmpBitmapEncoder(); + break; + case "gif": + encoder = new GifBitmapEncoder(); + break; + case "jpg": + encoder = new JpegBitmapEncoder(); + break; + case "png": + encoder = new PngBitmapEncoder(); + break; + case "tiff": + encoder = new TiffBitmapEncoder(); + break; + case "wmphoto": + encoder = new WmpBitmapEncoder(); + break; + default: + break; } - MemoryCache.Default.Set(key, buffer, policy); + if (encoder == null) + { + throw new NotSupportedException(string.Format("The bitmap format {0} is not supported.", format)); + } - var path = GetPath(key) + GetFileExtension(buffer); + memoryCache.Set(key, bitmap, policy, regionName); + + var path = string.Format("{0}.{1}", GetPath(key), format); try { Directory.CreateDirectory(Path.GetDirectoryName(path)); - using (var fileStream = new FileStream(path, FileMode.Create)) + using (var fileStream = new FileStream(path, FileMode.Create, FileAccess.Write)) { - fileStream.Write(buffer, 8, buffer.Length - 8); + encoder.Frames.Add(bitmap); + encoder.Save(fileStream); } - var expirationTime = DateTime.FromBinary(BitConverter.ToInt64(buffer, 0)); - File.SetLastAccessTimeUtc(path, expirationTime); - var fileSecurity = File.GetAccessControl(path); fileSecurity.AddAccessRule(fullControlRule); File.SetAccessControl(path, fileSecurity); } catch (Exception ex) { - Trace.TraceWarning("ImageFileCache: Writing file {0} failed: {1}", path, ex.Message); + Debug.WriteLine("ImageFileCache: Writing file {0} failed: {1}", path, ex.Message); } } @@ -253,7 +241,8 @@ namespace Caching public override object Remove(string key, string regionName = null) { var oldValue = Get(key, regionName); - MemoryCache.Default.Remove(key); + + memoryCache.Remove(key, regionName); try { @@ -273,7 +262,7 @@ namespace Caching private string GetPath(string key) { - return Path.Combine(directory, key); + return Path.Combine(rootFolder, key); } private static string FindFile(string path) @@ -283,34 +272,14 @@ namespace Caching return path; } - string directoryName = Path.GetDirectoryName(path); + string folderName = Path.GetDirectoryName(path); - if (Directory.Exists(directoryName)) + if (Directory.Exists(folderName)) { - return Directory.EnumerateFiles(directoryName, Path.GetFileName(path) + ".*").FirstOrDefault(); + return Directory.EnumerateFiles(folderName, Path.GetFileName(path) + ".*").FirstOrDefault(); } return null; } - - private static string GetFileExtension(byte[] buffer) - { - var fileType = imageFileTypes.FirstOrDefault(t => - { - int i = 0; - - if (t.Item2.Length <= buffer.Length - 8) - { - while (i < t.Item2.Length && t.Item2[i] == buffer[i + 8]) - { - i++; - } - } - - return i == t.Item2.Length; - }); - - return fileType != null ? fileType.Item1 : ".bin"; - } } } diff --git a/Caching/ImageFileCache/Properties/AssemblyInfo.cs b/Caching/ImageFileCache.WPF/Properties/AssemblyInfo.cs similarity index 75% rename from Caching/ImageFileCache/Properties/AssemblyInfo.cs rename to Caching/ImageFileCache.WPF/Properties/AssemblyInfo.cs index 313744d6..5d71f9eb 100644 --- a/Caching/ImageFileCache/Properties/AssemblyInfo.cs +++ b/Caching/ImageFileCache.WPF/Properties/AssemblyInfo.cs @@ -1,14 +1,14 @@ using System.Reflection; using System.Runtime.InteropServices; -[assembly: AssemblyTitle("ImageFileCache")] +[assembly: AssemblyTitle("XAML Map Control ImageFileCache (WPF)")] [assembly: AssemblyDescription("ObjectCache implementation based on local image files")] [assembly: AssemblyProduct("XAML Map Control")] [assembly: AssemblyCompany("Clemens Fischer")] [assembly: AssemblyCopyright("Copyright © 2014 Clemens Fischer")] [assembly: AssemblyTrademark("")] -[assembly: AssemblyVersion("2.3.1")] -[assembly: AssemblyFileVersion("2.3.1")] +[assembly: AssemblyVersion("2.4.0")] +[assembly: AssemblyFileVersion("2.4.0")] [assembly: AssemblyConfiguration("")] [assembly: AssemblyCulture("")] [assembly: ComVisible(false)] diff --git a/Caching/ImageFileCache.WinRT/ImageFileCache.WinRT.csproj b/Caching/ImageFileCache.WinRT/ImageFileCache.WinRT.csproj new file mode 100644 index 00000000..148d5184 --- /dev/null +++ b/Caching/ImageFileCache.WinRT/ImageFileCache.WinRT.csproj @@ -0,0 +1,66 @@ + + + + + 12.0 + Debug + AnyCPU + {F789647E-96F7-43E3-A895-FA3FE8D01260} + Library + Properties + MapControl.Caching + ImageFileCache.WinRT + en-US + 512 + {BC8A1FFA-BEE3-4634-8014-F334798102B3};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} + Windows + 8.1 + + + true + full + false + bin\Debug\ + DEBUG;TRACE;NETFX_CORE;WINDOWS_APP + prompt + 4 + + + none + true + bin\Release\ + TRACE;NETFX_CORE;WINDOWS_APP + prompt + 4 + + + true + + + ..\..\MapControl.snk + + + + + MapControl.snk + + + + + + + + + {63cefdf7-5170-43b6-86f8-5c4a383a1615} + MapControl.WinRT + + + + + \ No newline at end of file diff --git a/Caching/ImageFileCache.WinRT/ImageFileCache.cs b/Caching/ImageFileCache.WinRT/ImageFileCache.cs new file mode 100644 index 00000000..113b0ba2 --- /dev/null +++ b/Caching/ImageFileCache.WinRT/ImageFileCache.cs @@ -0,0 +1,94 @@ +// XAML Map Control - http://xamlmapcontrol.codeplex.com/ +// Copyright © 2014 Clemens Fischer +// Licensed under the Microsoft Public License (Ms-PL) + +using System; +using System.Diagnostics; +using System.Threading.Tasks; +using Windows.Storage; +using Windows.Storage.Streams; + +namespace MapControl.Caching +{ + public class ImageFileCache : IImageCache + { + private readonly string name; + private StorageFolder rootFolder; + + public ImageFileCache(string name = null, StorageFolder folder = null) + { + if (string.IsNullOrWhiteSpace(name)) + { + name = TileImageLoader.DefaultCacheName; + } + + if (folder == null) + { + folder = TileImageLoader.DefaultCacheFolder; + } + + this.name = name; + + folder.CreateFolderAsync(name, CreationCollisionOption.OpenIfExists).Completed = (o, s) => + { + rootFolder = o.GetResults(); + Debug.WriteLine("Created ImageFileCache in {0}.", rootFolder.Path); + }; + } + + public virtual async Task GetAsync(string key) + { + var item = await rootFolder.TryGetItemAsync(key); + + if (item == null || !item.IsOfType(StorageItemTypes.File)) + { + return null; + } + + var file = (StorageFile)item; + + var cacheItem = new ImageCacheItem + { + Buffer = await FileIO.ReadBufferAsync(file), + }; + + try + { + // Use ImageProperties.DateTaken to get expiration date + var imageProperties = await file.Properties.GetImagePropertiesAsync(); + cacheItem.Expires = imageProperties.DateTaken.UtcDateTime; + } + catch + { + } + + return cacheItem; + } + + public virtual async Task SetAsync(string key, IBuffer buffer, DateTime expires) + { + try + { + var names = key.Split('\\'); + var folder = rootFolder; + + for (int i = 0; i < names.Length - 1; i++) + { + folder = await folder.CreateFolderAsync(names[i], CreationCollisionOption.OpenIfExists); + } + + var file = await folder.CreateFileAsync(names[names.Length - 1], CreationCollisionOption.ReplaceExisting); + await FileIO.WriteBufferAsync(file, buffer); + + // Use ImageProperties.DateTaken to store expiration date + var imageProperties = await file.Properties.GetImagePropertiesAsync(); + imageProperties.DateTaken = expires; + await imageProperties.SavePropertiesAsync(); + } + catch (Exception ex) + { + Debug.WriteLine(ex.Message); + } + } + } +} diff --git a/Caching/ImageFileCache.WinRT/Properties/AssemblyInfo.cs b/Caching/ImageFileCache.WinRT/Properties/AssemblyInfo.cs new file mode 100644 index 00000000..62f128f7 --- /dev/null +++ b/Caching/ImageFileCache.WinRT/Properties/AssemblyInfo.cs @@ -0,0 +1,14 @@ +using System.Reflection; +using System.Runtime.InteropServices; + +[assembly: AssemblyTitle("XAML Map Control ImageFileCache (WinRT)")] +[assembly: AssemblyDescription("IImageCache implementation based on local image files")] +[assembly: AssemblyProduct("XAML Map Control")] +[assembly: AssemblyCompany("Clemens Fischer")] +[assembly: AssemblyCopyright("Copyright © 2014 Clemens Fischer")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyVersion("2.4.0")] +[assembly: AssemblyFileVersion("2.4.0")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCulture("")] +[assembly: ComVisible(false)] diff --git a/MapControl.sln b/MapControl.sln index 8f9d1105..622a4edd 100644 --- a/MapControl.sln +++ b/MapControl.sln @@ -1,44 +1,72 @@  Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio 2013 -VisualStudioVersion = 12.0.30723.0 +VisualStudioVersion = 12.0.31101.0 MinimumVisualStudioVersion = 10.0.40219.1 -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FileDbCache", "Caching\FileDbCache\FileDbCache.csproj", "{EF44F661-B98A-4676-927F-85D138F82300}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FileDbCache.WinRT", "Caching\FileDbCache.WinRT\FileDbCache.WinRT.csproj", "{C7BF2B18-CC74-430B-BCB2-600304EFA3D8}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ImageFileCache", "Caching\ImageFileCache\ImageFileCache.csproj", "{86470440-FEE2-4120-AF5A-3762FB9C536F}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FileDbCache.WPF", "Caching\FileDbCache.WPF\FileDbCache.WPF.csproj", "{EF44F661-B98A-4676-927F-85D138F82300}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ImageFileCache.WinRT", "Caching\ImageFileCache.WinRT\ImageFileCache.WinRT.csproj", "{F789647E-96F7-43E3-A895-FA3FE8D01260}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ImageFileCache.WPF", "Caching\ImageFileCache.WPF\ImageFileCache.WPF.csproj", "{86470440-FEE2-4120-AF5A-3762FB9C536F}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MapControl.Silverlight", "MapControl\MapControl.Silverlight.csproj", "{EB133B78-DEFF-416A-8F0C-89E54D766576}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MapControl.WinRT", "MapControl\WinRT\MapControl.WinRT.csproj", "{63CEFDF7-5170-43B6-86F8-5C4A383A1615}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MapControl.WPF", "MapControl\MapControl.WPF.csproj", "{226F3575-B683-446D-A2F0-181291DC8787}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PhoneApplication", "SampleApps\PhoneApplication\PhoneApplication.csproj", "{8D0A57DF-FABF-4AEE-8768-9C18B2B43CA9}" EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SilverlightApplication", "SampleApps\SilverlightApplication\SilverlightApplication.csproj", "{CBA8C535-CCA3-4F60-8D3E-0E25791CBD21}" EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SilverlightApplication.Web", "SampleApps\SilverlightApplication.Web\SilverlightApplication.Web.csproj", "{177C4EF8-0B0A-426E-BDCC-168DC10AC1C1}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MapControl.WPF", "MapControl\MapControl.WPF.csproj", "{226F3575-B683-446D-A2F0-181291DC8787}" -EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "StoreApplication", "SampleApps\StoreApplication\StoreApplication.csproj", "{747A3F84-E11F-4EC8-9463-98BBB1E0D0A4}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MapControl.WinRT", "MapControl\WinRT\MapControl.WinRT.csproj", "{63CEFDF7-5170-43B6-86F8-5C4A383A1615}" -EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WpfApplication", "SampleApps\WpfApplication\WpfApplication.csproj", "{9949326E-9261-4F95-89B1-151F60498951}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PhoneApplication", "SampleApps\PhoneApplication\PhoneApplication.csproj", "{8D0A57DF-FABF-4AEE-8768-9C18B2B43CA9}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MapControl.Silverlight", "MapControl\MapControl.Silverlight.csproj", "{EB133B78-DEFF-416A-8F0C-89E54D766576}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MapControl.PhoneSilverlight", "MapControl\MapControl.PhoneSilverlight.csproj", "{3499D618-2846-4FCE-A418-7D211FDBDCB3}" -EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU Release|Any CPU = Release|Any CPU EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution + {C7BF2B18-CC74-430B-BCB2-600304EFA3D8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {C7BF2B18-CC74-430B-BCB2-600304EFA3D8}.Debug|Any CPU.Build.0 = Debug|Any CPU + {C7BF2B18-CC74-430B-BCB2-600304EFA3D8}.Release|Any CPU.ActiveCfg = Release|Any CPU + {C7BF2B18-CC74-430B-BCB2-600304EFA3D8}.Release|Any CPU.Build.0 = Release|Any CPU {EF44F661-B98A-4676-927F-85D138F82300}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {EF44F661-B98A-4676-927F-85D138F82300}.Debug|Any CPU.Build.0 = Debug|Any CPU {EF44F661-B98A-4676-927F-85D138F82300}.Release|Any CPU.ActiveCfg = Release|Any CPU {EF44F661-B98A-4676-927F-85D138F82300}.Release|Any CPU.Build.0 = Release|Any CPU + {F789647E-96F7-43E3-A895-FA3FE8D01260}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {F789647E-96F7-43E3-A895-FA3FE8D01260}.Debug|Any CPU.Build.0 = Debug|Any CPU + {F789647E-96F7-43E3-A895-FA3FE8D01260}.Release|Any CPU.ActiveCfg = Release|Any CPU + {F789647E-96F7-43E3-A895-FA3FE8D01260}.Release|Any CPU.Build.0 = Release|Any CPU {86470440-FEE2-4120-AF5A-3762FB9C536F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {86470440-FEE2-4120-AF5A-3762FB9C536F}.Debug|Any CPU.Build.0 = Debug|Any CPU {86470440-FEE2-4120-AF5A-3762FB9C536F}.Release|Any CPU.ActiveCfg = Release|Any CPU {86470440-FEE2-4120-AF5A-3762FB9C536F}.Release|Any CPU.Build.0 = Release|Any CPU + {EB133B78-DEFF-416A-8F0C-89E54D766576}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {EB133B78-DEFF-416A-8F0C-89E54D766576}.Debug|Any CPU.Build.0 = Debug|Any CPU + {EB133B78-DEFF-416A-8F0C-89E54D766576}.Release|Any CPU.ActiveCfg = Release|Any CPU + {EB133B78-DEFF-416A-8F0C-89E54D766576}.Release|Any CPU.Build.0 = Release|Any CPU + {63CEFDF7-5170-43B6-86F8-5C4A383A1615}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {63CEFDF7-5170-43B6-86F8-5C4A383A1615}.Debug|Any CPU.Build.0 = Debug|Any CPU + {63CEFDF7-5170-43B6-86F8-5C4A383A1615}.Release|Any CPU.ActiveCfg = Release|Any CPU + {63CEFDF7-5170-43B6-86F8-5C4A383A1615}.Release|Any CPU.Build.0 = Release|Any CPU + {226F3575-B683-446D-A2F0-181291DC8787}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {226F3575-B683-446D-A2F0-181291DC8787}.Debug|Any CPU.Build.0 = Debug|Any CPU + {226F3575-B683-446D-A2F0-181291DC8787}.Release|Any CPU.ActiveCfg = Release|Any CPU + {226F3575-B683-446D-A2F0-181291DC8787}.Release|Any CPU.Build.0 = Release|Any CPU + {8D0A57DF-FABF-4AEE-8768-9C18B2B43CA9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {8D0A57DF-FABF-4AEE-8768-9C18B2B43CA9}.Debug|Any CPU.Build.0 = Debug|Any CPU + {8D0A57DF-FABF-4AEE-8768-9C18B2B43CA9}.Debug|Any CPU.Deploy.0 = Debug|Any CPU + {8D0A57DF-FABF-4AEE-8768-9C18B2B43CA9}.Release|Any CPU.ActiveCfg = Release|Any CPU + {8D0A57DF-FABF-4AEE-8768-9C18B2B43CA9}.Release|Any CPU.Build.0 = Release|Any CPU + {8D0A57DF-FABF-4AEE-8768-9C18B2B43CA9}.Release|Any CPU.Deploy.0 = Release|Any CPU {CBA8C535-CCA3-4F60-8D3E-0E25791CBD21}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {CBA8C535-CCA3-4F60-8D3E-0E25791CBD21}.Debug|Any CPU.Build.0 = Debug|Any CPU {CBA8C535-CCA3-4F60-8D3E-0E25791CBD21}.Release|Any CPU.ActiveCfg = Release|Any CPU @@ -47,38 +75,16 @@ Global {177C4EF8-0B0A-426E-BDCC-168DC10AC1C1}.Debug|Any CPU.Build.0 = Debug|Any CPU {177C4EF8-0B0A-426E-BDCC-168DC10AC1C1}.Release|Any CPU.ActiveCfg = Release|Any CPU {177C4EF8-0B0A-426E-BDCC-168DC10AC1C1}.Release|Any CPU.Build.0 = Release|Any CPU - {226F3575-B683-446D-A2F0-181291DC8787}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {226F3575-B683-446D-A2F0-181291DC8787}.Debug|Any CPU.Build.0 = Debug|Any CPU - {226F3575-B683-446D-A2F0-181291DC8787}.Release|Any CPU.ActiveCfg = Release|Any CPU - {226F3575-B683-446D-A2F0-181291DC8787}.Release|Any CPU.Build.0 = Release|Any CPU {747A3F84-E11F-4EC8-9463-98BBB1E0D0A4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {747A3F84-E11F-4EC8-9463-98BBB1E0D0A4}.Debug|Any CPU.Build.0 = Debug|Any CPU {747A3F84-E11F-4EC8-9463-98BBB1E0D0A4}.Debug|Any CPU.Deploy.0 = Debug|Any CPU {747A3F84-E11F-4EC8-9463-98BBB1E0D0A4}.Release|Any CPU.ActiveCfg = Release|Any CPU {747A3F84-E11F-4EC8-9463-98BBB1E0D0A4}.Release|Any CPU.Build.0 = Release|Any CPU {747A3F84-E11F-4EC8-9463-98BBB1E0D0A4}.Release|Any CPU.Deploy.0 = Release|Any CPU - {63CEFDF7-5170-43B6-86F8-5C4A383A1615}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {63CEFDF7-5170-43B6-86F8-5C4A383A1615}.Debug|Any CPU.Build.0 = Debug|Any CPU - {63CEFDF7-5170-43B6-86F8-5C4A383A1615}.Release|Any CPU.ActiveCfg = Release|Any CPU - {63CEFDF7-5170-43B6-86F8-5C4A383A1615}.Release|Any CPU.Build.0 = Release|Any CPU {9949326E-9261-4F95-89B1-151F60498951}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {9949326E-9261-4F95-89B1-151F60498951}.Debug|Any CPU.Build.0 = Debug|Any CPU {9949326E-9261-4F95-89B1-151F60498951}.Release|Any CPU.ActiveCfg = Release|Any CPU {9949326E-9261-4F95-89B1-151F60498951}.Release|Any CPU.Build.0 = Release|Any CPU - {8D0A57DF-FABF-4AEE-8768-9C18B2B43CA9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {8D0A57DF-FABF-4AEE-8768-9C18B2B43CA9}.Debug|Any CPU.Build.0 = Debug|Any CPU - {8D0A57DF-FABF-4AEE-8768-9C18B2B43CA9}.Debug|Any CPU.Deploy.0 = Debug|Any CPU - {8D0A57DF-FABF-4AEE-8768-9C18B2B43CA9}.Release|Any CPU.ActiveCfg = Release|Any CPU - {8D0A57DF-FABF-4AEE-8768-9C18B2B43CA9}.Release|Any CPU.Build.0 = Release|Any CPU - {8D0A57DF-FABF-4AEE-8768-9C18B2B43CA9}.Release|Any CPU.Deploy.0 = Release|Any CPU - {EB133B78-DEFF-416A-8F0C-89E54D766576}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {EB133B78-DEFF-416A-8F0C-89E54D766576}.Debug|Any CPU.Build.0 = Debug|Any CPU - {EB133B78-DEFF-416A-8F0C-89E54D766576}.Release|Any CPU.ActiveCfg = Release|Any CPU - {EB133B78-DEFF-416A-8F0C-89E54D766576}.Release|Any CPU.Build.0 = Release|Any CPU - {3499D618-2846-4FCE-A418-7D211FDBDCB3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {3499D618-2846-4FCE-A418-7D211FDBDCB3}.Debug|Any CPU.Build.0 = Debug|Any CPU - {3499D618-2846-4FCE-A418-7D211FDBDCB3}.Release|Any CPU.ActiveCfg = Release|Any CPU - {3499D618-2846-4FCE-A418-7D211FDBDCB3}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/MapControl/BingMapsTileLayer.cs b/MapControl/BingMapsTileLayer.cs index 6c5b15b0..6d01b779 100644 --- a/MapControl/BingMapsTileLayer.cs +++ b/MapControl/BingMapsTileLayer.cs @@ -57,8 +57,7 @@ namespace MapControl var request = (HttpWebRequest)asyncResult.AsyncState; using (var response = request.EndGetResponse(asyncResult)) - using (var responseStream = response.GetResponseStream()) - using (var xmlReader = XmlReader.Create(responseStream)) + using (var xmlReader = XmlReader.Create(response.GetResponseStream())) { ReadImageryMetadataResponse(xmlReader); } diff --git a/MapControl/HyperlinkText.cs b/MapControl/HyperlinkText.cs index 767cf72a..29198ba7 100644 --- a/MapControl/HyperlinkText.cs +++ b/MapControl/HyperlinkText.cs @@ -21,7 +21,7 @@ namespace MapControl /// Converts text containing hyperlinks in markdown syntax [text](url) /// to a collection of Run and Hyperlink inlines. ///
    - public static ICollection ToInlines(this string text) + public static List ToInlines(this string text) { var inlines = new List(); diff --git a/MapControl/IMapElement.cs b/MapControl/IMapElement.cs index 9c8d5b55..33faaa3b 100644 --- a/MapControl/IMapElement.cs +++ b/MapControl/IMapElement.cs @@ -6,8 +6,6 @@ namespace MapControl { public interface IMapElement { - MapBase ParentMap { get; } - - void SetParentMap(MapBase parentMap); + MapBase ParentMap { get; set; } } } diff --git a/MapControl/ITileImageLoader.cs b/MapControl/ITileImageLoader.cs new file mode 100644 index 00000000..72b719ad --- /dev/null +++ b/MapControl/ITileImageLoader.cs @@ -0,0 +1,14 @@ +// XAML Map Control - http://xamlmapcontrol.codeplex.com/ +// Copyright © 2014 Clemens Fischer +// Licensed under the Microsoft Public License (Ms-PL) + +using System.Collections.Generic; + +namespace MapControl +{ + public interface ITileImageLoader + { + void BeginLoadTiles(TileLayer tileLayer, IEnumerable tiles); + void CancelLoadTiles(TileLayer tileLayer); + } +} diff --git a/MapControl/ImageCache.WinRT.cs b/MapControl/ImageCache.WinRT.cs new file mode 100644 index 00000000..6e0f9cea --- /dev/null +++ b/MapControl/ImageCache.WinRT.cs @@ -0,0 +1,22 @@ +// XAML Map Control - http://xamlmapcontrol.codeplex.com/ +// Copyright © 2014 Clemens Fischer +// Licensed under the Microsoft Public License (Ms-PL) + +using System; +using System.Threading.Tasks; +using Windows.Storage.Streams; + +namespace MapControl.Caching +{ + public class ImageCacheItem + { + public IBuffer Buffer { get; set; } + public DateTime Expires { get; set; } + } + + public interface IImageCache + { + Task GetAsync(string key); + Task SetAsync(string key, IBuffer buffer, DateTime expires); + } +} diff --git a/MapControl/ImageFileCache.WinRT.cs b/MapControl/ImageFileCache.WinRT.cs deleted file mode 100644 index 2639b0ae..00000000 --- a/MapControl/ImageFileCache.WinRT.cs +++ /dev/null @@ -1,67 +0,0 @@ -// XAML Map Control - http://xamlmapcontrol.codeplex.com/ -// Copyright © 2014 Clemens Fischer -// Licensed under the Microsoft Public License (Ms-PL) - -using System; -using System.Diagnostics; -using System.IO; -using System.Threading.Tasks; -using Windows.Storage; -using Windows.Storage.Streams; - -namespace MapControl -{ - public class ImageFileCache : IObjectCache - { - private readonly IStorageFolder rootFolder; - - public ImageFileCache() - { - rootFolder = ApplicationData.Current.TemporaryFolder; - } - - public ImageFileCache(IStorageFolder folder) - { - if (folder == null) - { - throw new ArgumentNullException("The parameter folder must not be null."); - } - - rootFolder = folder; - } - - public async Task GetAsync(string key) - { - try - { - return await PathIO.ReadBufferAsync(Path.Combine(rootFolder.Path, key)); - } - catch - { - return null; - } - } - - public async Task SetAsync(string key, object value) - { - try - { - var buffer = (IBuffer)value; - var names = key.Split('\\'); - var folder = rootFolder; - - for (int i = 0; i < names.Length - 1; i++) - { - folder = await folder.CreateFolderAsync(names[i], CreationCollisionOption.OpenIfExists); - } - - var file = await folder.CreateFileAsync(names[names.Length - 1], CreationCollisionOption.ReplaceExisting); - await FileIO.WriteBufferAsync(file, buffer); - } - catch (Exception ex) - { - Debug.WriteLine(ex.Message); - } - } - } -} diff --git a/MapControl/ImageTileSource.WPF.cs b/MapControl/ImageTileSource.WPF.cs index 2af21f5b..4ff5eaa1 100644 --- a/MapControl/ImageTileSource.WPF.cs +++ b/MapControl/ImageTileSource.WPF.cs @@ -30,15 +30,16 @@ namespace MapControl { if (IsAsync) { - var buffer = new WebClient().DownloadData(uri); + var request = HttpWebRequest.CreateHttp(uri); + request.UserAgent = TileImageLoader.HttpUserAgent; - if (buffer != null) + using (var response = (HttpWebResponse)request.GetResponse()) + using (var responseStream = response.GetResponseStream()) + using (var memoryStream = new MemoryStream()) { - using (var stream = new MemoryStream(buffer)) - { - image = BitmapFrame.Create(stream, BitmapCreateOptions.None, BitmapCacheOption.OnLoad); - image.Freeze(); - } + responseStream.CopyTo(memoryStream); + memoryStream.Position = 0; + image = BitmapFrame.Create(memoryStream, BitmapCreateOptions.None, BitmapCacheOption.OnLoad); } } else diff --git a/MapControl/Int32Rect.cs b/MapControl/Int32Rect.cs index 2cbd9e8b..6dea4511 100644 --- a/MapControl/Int32Rect.cs +++ b/MapControl/Int32Rect.cs @@ -4,7 +4,7 @@ namespace MapControl { - internal struct Int32Rect + public struct Int32Rect { public Int32Rect(int x, int y, int width, int height) : this() diff --git a/MapControl/MapBase.Silverlight.WinRT.cs b/MapControl/MapBase.Silverlight.WinRT.cs index d1b207a0..fb41c2cf 100644 --- a/MapControl/MapBase.Silverlight.WinRT.cs +++ b/MapControl/MapBase.Silverlight.WinRT.cs @@ -2,14 +2,17 @@ // Copyright © 2014 Clemens Fischer // Licensed under the Microsoft Public License (Ms-PL) +using System; #if WINDOWS_RUNTIME using Windows.Foundation; using Windows.UI; using Windows.UI.Xaml; using Windows.UI.Xaml.Media; +using Windows.UI.Xaml.Controls; #else using System.Windows; using System.Windows.Media; +using System.Windows.Controls; #endif namespace MapControl @@ -45,7 +48,11 @@ namespace MapControl partial void Initialize() { - Background = new SolidColorBrush(Colors.Transparent); + // set Background by Style to enable resetting by ClearValue in RemoveTileLayers + var style = new Style(typeof(MapBase)); + style.Setters.Add(new Setter(Panel.BackgroundProperty, new SolidColorBrush(Colors.Transparent))); + Style = style; + Clip = new RectangleGeometry(); SizeChanged += OnRenderSizeChanged; @@ -58,11 +65,39 @@ namespace MapControl UpdateTransform(); } + private void SetViewportTransform(Point mapOrigin) + { + viewportTransform.Matrix = Matrix.Identity + .Translate(-mapOrigin.X, -mapOrigin.Y) + .Scale(ViewportScale, -ViewportScale) + .Rotate(Heading) + .Translate(viewportOrigin.X, viewportOrigin.Y); + } + + private void SetTileLayerTransform() + { + var scale = Math.Pow(2d, ZoomLevel - TileZoomLevel); + + tileLayerTransform.Matrix = Matrix.Identity + .Translate(TileGrid.X * TileSource.TileSize, TileGrid.Y * TileSource.TileSize) + .Scale(scale, scale) + .Translate(tileLayerOffset.X, tileLayerOffset.Y) + .RotateAt(Heading, viewportOrigin.X, viewportOrigin.Y); ; + } + private void SetTransformMatrixes() { - scaleTransform.Matrix = new Matrix(CenterScale, 0d, 0d, CenterScale, 0d, 0d); + scaleTransform.Matrix = Matrix.Identity.Scale(CenterScale, CenterScale); rotateTransform.Matrix = Matrix.Identity.Rotate(Heading); scaleRotateTransform.Matrix = scaleTransform.Matrix.Multiply(rotateTransform.Matrix); } + + private Matrix GetTileIndexMatrix(double scale) + { + return viewportTransform.Matrix + .Invert() // view to map coordinates + .Translate(180d, -180d) + .Scale(scale, -scale); // map coordinates to tile indices + } } } diff --git a/MapControl/MapBase.WPF.cs b/MapControl/MapBase.WPF.cs index 8f03fc95..0f710aab 100644 --- a/MapControl/MapBase.WPF.cs +++ b/MapControl/MapBase.WPF.cs @@ -2,6 +2,7 @@ // Copyright © 2014 Clemens Fischer // Licensed under the Microsoft Public License (Ms-PL) +using System; using System.Windows; using System.Windows.Controls; using System.Windows.Media; @@ -11,7 +12,7 @@ namespace MapControl public partial class MapBase { public static readonly DependencyProperty ForegroundProperty = - System.Windows.Controls.Control.ForegroundProperty.AddOwner(typeof(MapBase)); + Control.ForegroundProperty.AddOwner(typeof(MapBase)); public static readonly DependencyProperty CenterProperty = DependencyProperty.Register( "Center", typeof(Location), typeof(MapBase), new FrameworkPropertyMetadata( @@ -64,13 +65,50 @@ namespace MapControl UpdateTransform(); } + private void SetViewportTransform(Point mapOrigin) + { + var transform = Matrix.Identity; + transform.Translate(-mapOrigin.X, -mapOrigin.Y); + transform.Scale(ViewportScale, -ViewportScale); + transform.Rotate(Heading); + transform.Translate(viewportOrigin.X, viewportOrigin.Y); + + viewportTransform.Matrix = transform; + } + + private void SetTileLayerTransform() + { + var scale = Math.Pow(2d, ZoomLevel - TileZoomLevel); + var transform = Matrix.Identity; + transform.Translate(TileGrid.X * TileSource.TileSize, TileGrid.Y * TileSource.TileSize); + transform.Scale(scale, scale); + transform.Translate(tileLayerOffset.X, tileLayerOffset.Y); + transform.RotateAt(Heading, viewportOrigin.X, viewportOrigin.Y); + + tileLayerTransform.Matrix = transform; + } + private void SetTransformMatrixes() { - Matrix rotateMatrix = Matrix.Identity; + var rotateMatrix = Matrix.Identity; rotateMatrix.Rotate(Heading); rotateTransform.Matrix = rotateMatrix; - scaleTransform.Matrix = new Matrix(CenterScale, 0d, 0d, CenterScale, 0d, 0d); - scaleRotateTransform.Matrix = scaleTransform.Matrix * rotateMatrix; + + var scaleMatrix = Matrix.Identity; + scaleMatrix.Scale(CenterScale, CenterScale); + scaleTransform.Matrix = scaleMatrix; + + scaleRotateTransform.Matrix = scaleMatrix * rotateMatrix; + } + + private Matrix GetTileIndexMatrix(double scale) + { + var transform = viewportTransform.Matrix; + transform.Invert(); // view to map coordinates + transform.Translate(180d, -180d); + transform.Scale(scale, -scale); // map coordinates to tile indices + + return transform; } } } diff --git a/MapControl/MapBase.cs b/MapControl/MapBase.cs index e5f129ff..02ad37e3 100644 --- a/MapControl/MapBase.cs +++ b/MapControl/MapBase.cs @@ -3,6 +3,7 @@ // Licensed under the Microsoft Public License (Ms-PL) using System; +using System.Collections.Generic; using System.Collections.Specialized; using System.Linq; #if WINDOWS_RUNTIME @@ -14,6 +15,7 @@ using Windows.UI.Xaml.Media.Animation; using System.Windows; using System.Windows.Media; using System.Windows.Media.Animation; +using System.Windows.Threading; #endif namespace MapControl @@ -28,17 +30,17 @@ namespace MapControl { private const double MaximumZoomLevel = 22d; - public static readonly DependencyProperty TileLayersProperty = DependencyProperty.Register( - "TileLayers", typeof(TileLayerCollection), typeof(MapBase), new PropertyMetadata(null, - (o, e) => ((MapBase)o).TileLayersPropertyChanged((TileLayerCollection)e.OldValue, (TileLayerCollection)e.NewValue))); + public static TimeSpan TileUpdateInterval = TimeSpan.FromSeconds(0.5); + public static TimeSpan AnimationDuration = TimeSpan.FromSeconds(0.3); + public static EasingFunctionBase AnimationEasingFunction = new QuadraticEase { EasingMode = EasingMode.EaseOut }; public static readonly DependencyProperty TileLayerProperty = DependencyProperty.Register( "TileLayer", typeof(TileLayer), typeof(MapBase), new PropertyMetadata(null, (o, e) => ((MapBase)o).TileLayerPropertyChanged((TileLayer)e.NewValue))); - public static readonly DependencyProperty TileOpacityProperty = DependencyProperty.Register( - "TileOpacity", typeof(double), typeof(MapBase), new PropertyMetadata(1d, - (o, e) => ((MapBase)o).tileContainer.Opacity = (double)e.NewValue)); + public static readonly DependencyProperty TileLayersProperty = DependencyProperty.Register( + "TileLayers", typeof(TileLayerCollection), typeof(MapBase), new PropertyMetadata(null, + (o, e) => ((MapBase)o).TileLayersPropertyChanged((TileLayerCollection)e.OldValue, (TileLayerCollection)e.NewValue))); public static readonly DependencyProperty MinZoomLevelProperty = DependencyProperty.Register( "MinZoomLevel", typeof(double), typeof(MapBase), new PropertyMetadata(1d, @@ -52,29 +54,32 @@ namespace MapControl "CenterPoint", typeof(Point), typeof(MapBase), new PropertyMetadata(new Point(), (o, e) => ((MapBase)o).CenterPointPropertyChanged((Point)e.NewValue))); - private readonly TileContainer tileContainer = new TileContainer(); + private readonly PanelBase tileLayerPanel = new PanelBase(); + private readonly DispatcherTimer tileUpdateTimer = new DispatcherTimer { Interval = TileUpdateInterval }; private readonly MapTransform mapTransform = new MercatorTransform(); + private readonly MatrixTransform viewportTransform = new MatrixTransform(); + private readonly MatrixTransform tileLayerTransform = new MatrixTransform(); private readonly MatrixTransform scaleTransform = new MatrixTransform(); private readonly MatrixTransform rotateTransform = new MatrixTransform(); private readonly MatrixTransform scaleRotateTransform = new MatrixTransform(); + private Location transformOrigin; private Point viewportOrigin; + private Point tileLayerOffset; private PointAnimation centerAnimation; private DoubleAnimation zoomLevelAnimation; private DoubleAnimation headingAnimation; - private Brush storedBackground; - private Brush storedForeground; private bool internalPropertyChange; public MapBase() { - SetParentMap(); - + Children.Add(tileLayerPanel); TileLayers = new TileLayerCollection(); - Children.Add(tileContainer); - Initialize(); + tileUpdateTimer.Tick += UpdateTiles; Loaded += OnLoaded; + + Initialize(); } partial void Initialize(); @@ -85,6 +90,11 @@ namespace MapControl /// public event EventHandler ViewportChanged; + /// + /// Raised when the TileZoomLevel or TileGrid properties have changed. + /// + public event EventHandler TileGridChanged; + /// /// Gets or sets the map foreground Brush. /// @@ -95,16 +105,7 @@ namespace MapControl } /// - /// Gets or sets the TileLayers used by this Map. - /// - public TileLayerCollection TileLayers - { - get { return (TileLayerCollection)GetValue(TileLayersProperty); } - set { SetValue(TileLayersProperty, value); } - } - - /// - /// Gets or sets the base TileLayer used by this Map, i.e. TileLayers[0]. + /// Gets or sets the base TileLayer used by the Map control. /// public TileLayer TileLayer { @@ -113,12 +114,15 @@ namespace MapControl } /// - /// Gets or sets the opacity of the tile layers. + /// Gets or sets optional multiple TileLayers that are used simultaneously. + /// The first element in the collection is equal to the value of the TileLayer property. + /// The additional TileLayers usually have transparent backgrounds and their IsOverlay + /// property is set to true. /// - public double TileOpacity + public TileLayerCollection TileLayers { - get { return (double)GetValue(TileOpacityProperty); } - set { SetValue(TileOpacityProperty, value); } + get { return (TileLayerCollection)GetValue(TileLayersProperty); } + set { SetValue(TileLayersProperty, value); } } /// @@ -208,7 +212,15 @@ namespace MapControl /// public Transform ViewportTransform { - get { return tileContainer.ViewportTransform; } + get { return viewportTransform; } + } + + /// + /// Gets the RenderTransform to be used by TileLayers, with origin at TileGrid.X and TileGrid.Y. + /// + public Transform TileLayerTransform + { + get { return tileLayerTransform; } } /// @@ -245,6 +257,16 @@ namespace MapControl /// public double CenterScale { get; private set; } + /// + /// Gets the zoom level to be used by TileLayers. + /// + public int TileZoomLevel { get; private set; } + + /// + /// Gets the tile grid to be used by TileLayers. + /// + public Int32Rect TileGrid { get; private set; } + /// /// Gets the map scale at the specified location as viewport coordinate units (pixels) per meter. /// @@ -258,7 +280,7 @@ namespace MapControl /// public Point LocationToViewportPoint(Location location) { - return ViewportTransform.Transform(mapTransform.Transform(location)); + return viewportTransform.Transform(mapTransform.Transform(location)); } /// @@ -266,7 +288,7 @@ namespace MapControl /// public Location ViewportPointToLocation(Point point) { - return mapTransform.Transform(ViewportTransform.Inverse.Transform(point)); + return mapTransform.Transform(viewportTransform.Inverse.Transform(point)); } /// @@ -365,105 +387,27 @@ namespace MapControl { if (southWest.Latitude < northEast.Latitude && southWest.Longitude < northEast.Longitude) { - var p1 = MapTransform.Transform(southWest); - var p2 = MapTransform.Transform(northEast); + var p1 = mapTransform.Transform(southWest); + var p2 = mapTransform.Transform(northEast); var lonScale = RenderSize.Width / (p2.X - p1.X) * 360d / TileSource.TileSize; var latScale = RenderSize.Height / (p2.Y - p1.Y) * 360d / TileSource.TileSize; var lonZoom = Math.Log(lonScale, 2d); var latZoom = Math.Log(latScale, 2d); TargetZoomLevel = Math.Min(lonZoom, latZoom); - TargetCenter = MapTransform.Transform(new Point((p1.X + p2.X) / 2d, (p1.Y + p2.Y) / 2d)); + TargetCenter = mapTransform.Transform(new Point((p1.X + p2.X) / 2d, (p1.Y + p2.Y) / 2d)); TargetHeading = 0d; } } - protected override void OnViewportChanged() - { - base.OnViewportChanged(); - - if (ViewportChanged != null) - { - ViewportChanged(this, EventArgs.Empty); - } - } - private void OnLoaded(object sender, RoutedEventArgs e) { Loaded -= OnLoaded; - if (TileLayer == null) + if (tileLayerPanel.Children.Count == 0 && !Children.OfType().Any()) { TileLayer = TileLayer.Default; } - - UpdateTransform(); - } - - private void TileLayerCollectionChanged(object sender, NotifyCollectionChangedEventArgs e) - { - switch (e.Action) - { - case NotifyCollectionChangedAction.Add: - tileContainer.AddTileLayers(e.NewStartingIndex, e.NewItems.Cast()); - break; - - case NotifyCollectionChangedAction.Remove: - tileContainer.RemoveTileLayers(e.OldStartingIndex, e.OldItems.Count); - break; -#if !SILVERLIGHT - case NotifyCollectionChangedAction.Move: -#endif - case NotifyCollectionChangedAction.Replace: - tileContainer.RemoveTileLayers(e.NewStartingIndex, e.OldItems.Count); - tileContainer.AddTileLayers(e.NewStartingIndex, e.NewItems.Cast()); - break; - - case NotifyCollectionChangedAction.Reset: - tileContainer.ClearTileLayers(); - if (e.NewItems != null) - { - tileContainer.AddTileLayers(0, e.NewItems.Cast()); - } - break; - - default: - break; - } - - var firstTileLayer = TileLayers.FirstOrDefault(); - - if (TileLayer != firstTileLayer) - { - TileLayer = firstTileLayer; - } - } - - private void TileLayersPropertyChanged(TileLayerCollection oldTileLayers, TileLayerCollection newTileLayers) - { - tileContainer.ClearTileLayers(); - - if (oldTileLayers != null) - { - oldTileLayers.CollectionChanged -= TileLayerCollectionChanged; - } - - if (newTileLayers != null) - { - newTileLayers.CollectionChanged += TileLayerCollectionChanged; - tileContainer.AddTileLayers(0, newTileLayers); - - var firstTileLayer = TileLayers.FirstOrDefault(); - - if (TileLayer != firstTileLayer) - { - TileLayer = firstTileLayer; - } - } - else - { - TileLayer = null; - } } private void TileLayerPropertyChanged(TileLayer tileLayer) @@ -484,35 +428,94 @@ namespace MapControl TileLayers[0] = tileLayer; } } + } - if (tileLayer != null && tileLayer.Background != null) + private void TileLayersPropertyChanged(TileLayerCollection oldTileLayers, TileLayerCollection newTileLayers) + { + if (oldTileLayers != null) { - if (storedBackground == null) + oldTileLayers.CollectionChanged -= TileLayerCollectionChanged; + RemoveTileLayers(0, oldTileLayers.Count); + } + + if (newTileLayers != null) + { + AddTileLayers(0, newTileLayers); + newTileLayers.CollectionChanged += TileLayerCollectionChanged; + } + + TileLayer = TileLayers.FirstOrDefault(); + } + + private void TileLayerCollectionChanged(object sender, NotifyCollectionChangedEventArgs e) + { + switch (e.Action) + { + case NotifyCollectionChangedAction.Add: + AddTileLayers(e.NewStartingIndex, e.NewItems.Cast()); + break; + + case NotifyCollectionChangedAction.Remove: + RemoveTileLayers(e.OldStartingIndex, e.OldItems.Count); + break; +#if !SILVERLIGHT + case NotifyCollectionChangedAction.Move: +#endif + case NotifyCollectionChangedAction.Replace: + RemoveTileLayers(e.NewStartingIndex, e.OldItems.Count); + AddTileLayers(e.NewStartingIndex, e.NewItems.Cast()); + break; + + case NotifyCollectionChangedAction.Reset: + if (e.OldItems != null) + { + RemoveTileLayers(0, e.OldItems.Count); + } + if (e.NewItems != null) + { + AddTileLayers(0, e.NewItems.Cast()); + } + break; + + default: + break; + } + + TileLayer = TileLayers.FirstOrDefault(); + } + + private void AddTileLayers(int index, IEnumerable tileLayers) + { + foreach (var tileLayer in tileLayers) + { + if (index == 0) { - storedBackground = Background; + if (tileLayer.Background != null) + { + Background = tileLayer.Background; + } + + if (tileLayer.Foreground != null) + { + Foreground = tileLayer.Foreground; + } } - Background = tileLayer.Background; + tileLayerPanel.Children.Insert(index++, tileLayer); } - else if (storedBackground != null) + } + + private void RemoveTileLayers(int index, int count) + { + while (count-- > 0) { - Background = storedBackground; - storedBackground = null; + tileLayerPanel.Children.RemoveAt(index + count); } - if (tileLayer != null && tileLayer.Foreground != null) + if (index == 0) { - if (storedForeground == null) - { - storedForeground = Foreground; - } - - Foreground = tileLayer.Foreground; - } - else if (storedForeground != null) - { - Foreground = storedForeground; - storedForeground = null; + ClearValue(BackgroundProperty); + ClearValue(ForegroundProperty); } } @@ -552,7 +555,7 @@ namespace MapControl if (centerAnimation == null) { InternalSetValue(TargetCenterProperty, center); - InternalSetValue(CenterPointProperty, MapTransform.Transform(center)); + InternalSetValue(CenterPointProperty, mapTransform.Transform(center)); } } } @@ -573,10 +576,10 @@ namespace MapControl // animate private CenterPoint property by PointAnimation centerAnimation = new PointAnimation { - From = MapTransform.Transform(Center), - To = MapTransform.Transform(targetCenter, Center.Longitude), - Duration = Settings.MapAnimationDuration, - EasingFunction = Settings.MapAnimationEasingFunction, + From = mapTransform.Transform(Center), + To = mapTransform.Transform(targetCenter, Center.Longitude), + Duration = AnimationDuration, + EasingFunction = AnimationEasingFunction, FillBehavior = FillBehavior.HoldEnd }; @@ -594,7 +597,7 @@ namespace MapControl centerAnimation = null; InternalSetValue(CenterProperty, TargetCenter); - InternalSetValue(CenterPointProperty, MapTransform.Transform(TargetCenter)); + InternalSetValue(CenterPointProperty, mapTransform.Transform(TargetCenter)); RemoveAnimation(CenterPointProperty); // remove holding animation in WPF ResetTransformOrigin(); @@ -607,7 +610,7 @@ namespace MapControl if (!internalPropertyChange) { centerPoint.X = Location.NormalizeLongitude(centerPoint.X); - InternalSetValue(CenterProperty, MapTransform.Transform(centerPoint)); + InternalSetValue(CenterProperty, mapTransform.Transform(centerPoint)); ResetTransformOrigin(); UpdateTransform(); } @@ -680,8 +683,8 @@ namespace MapControl zoomLevelAnimation = new DoubleAnimation { To = targetZoomLevel, - Duration = Settings.MapAnimationDuration, - EasingFunction = Settings.MapAnimationEasingFunction, + Duration = AnimationDuration, + EasingFunction = AnimationEasingFunction, FillBehavior = FillBehavior.HoldEnd }; @@ -755,8 +758,8 @@ namespace MapControl headingAnimation = new DoubleAnimation { By = delta, - Duration = Settings.MapAnimationDuration, - EasingFunction = Settings.MapAnimationEasingFunction, + Duration = AnimationDuration, + EasingFunction = AnimationEasingFunction, FillBehavior = FillBehavior.HoldEnd }; @@ -784,7 +787,12 @@ namespace MapControl { Location center; - if (transformOrigin != null) + if (transformOrigin == null) + { + center = Center; + SetViewportTransform(center); + } + else { SetViewportTransform(transformOrigin); @@ -802,7 +810,7 @@ namespace MapControl if (centerAnimation == null) { InternalSetValue(TargetCenterProperty, center); - InternalSetValue(CenterPointProperty, MapTransform.Transform(center)); + InternalSetValue(CenterPointProperty, mapTransform.Transform(center)); } if (resetTransformOrigin) @@ -811,11 +819,6 @@ namespace MapControl SetViewportTransform(center); } } - else - { - center = Center; - SetViewportTransform(center); - } CenterScale = ViewportScale * mapTransform.RelativeScale(center) / TileSource.MetersPerDegree; // Pixels per meter at center latitude @@ -823,9 +826,77 @@ namespace MapControl OnViewportChanged(); } + protected override void OnViewportChanged() + { + base.OnViewportChanged(); + + var viewportChanged = ViewportChanged; + + if (viewportChanged != null) + { + viewportChanged(this, EventArgs.Empty); + } + } + private void SetViewportTransform(Location origin) { - ViewportScale = tileContainer.SetViewportTransform(ZoomLevel, Heading, mapTransform.Transform(origin), viewportOrigin); + var oldMapOriginX = (viewportOrigin.X - tileLayerOffset.X) / ViewportScale - 180d; + var mapOrigin = mapTransform.Transform(origin); + + ViewportScale = Math.Pow(2d, ZoomLevel) * TileSource.TileSize / 360d; + SetViewportTransform(mapOrigin); + + tileLayerOffset.X = viewportOrigin.X - (180d + mapOrigin.X) * ViewportScale; + tileLayerOffset.Y = viewportOrigin.Y - (180d - mapOrigin.Y) * ViewportScale; + + if (Math.Abs(mapOrigin.X - oldMapOriginX) > 180d) + { + // immediately handle map origin leap when map center moves across 180° longitude + UpdateTiles(this, EventArgs.Empty); + } + else + { + SetTileLayerTransform(); + tileUpdateTimer.Start(); + } + } + + private void UpdateTiles(object sender, object e) + { + tileUpdateTimer.Stop(); + + // relative size of scaled tile ranges from 0.75 to 1.5 (192 to 384 pixels) + var zoomLevelSwitchDelta = Math.Log(0.75, 2d); + var zoomLevel = (int)Math.Floor(ZoomLevel - zoomLevelSwitchDelta); + var transform = GetTileIndexMatrix((double)(1 << zoomLevel) / 360d); + + // tile indices of visible rectangle + var p1 = transform.Transform(new Point(0d, 0d)); + var p2 = transform.Transform(new Point(RenderSize.Width, 0d)); + var p3 = transform.Transform(new Point(0d, RenderSize.Height)); + var p4 = transform.Transform(new Point(RenderSize.Width, RenderSize.Height)); + + // index ranges of visible tiles + var x1 = (int)Math.Floor(Math.Min(p1.X, Math.Min(p2.X, Math.Min(p3.X, p4.X)))); + var y1 = (int)Math.Floor(Math.Min(p1.Y, Math.Min(p2.Y, Math.Min(p3.Y, p4.Y)))); + var x2 = (int)Math.Floor(Math.Max(p1.X, Math.Max(p2.X, Math.Max(p3.X, p4.X)))); + var y2 = (int)Math.Floor(Math.Max(p1.Y, Math.Max(p2.Y, Math.Max(p3.Y, p4.Y)))); + var grid = new Int32Rect(x1, y1, x2 - x1 + 1, y2 - y1 + 1); + + if (TileZoomLevel != zoomLevel || TileGrid != grid) + { + TileZoomLevel = zoomLevel; + TileGrid = grid; + + SetTileLayerTransform(); + + var tileGridChanged = TileGridChanged; + + if (tileGridChanged != null) + { + tileGridChanged(this, EventArgs.Empty); + } + } } } } diff --git a/MapControl/MapControl.PhoneSilverlight.csproj b/MapControl/MapControl.PhoneSilverlight.csproj index 958a2856..82a855b8 100644 --- a/MapControl/MapControl.PhoneSilverlight.csproj +++ b/MapControl/MapControl.PhoneSilverlight.csproj @@ -23,7 +23,7 @@ true full false - Bin\Debug + bin\Debug\ DEBUG;TRACE;SILVERLIGHT;WINDOWS_PHONE true true @@ -33,7 +33,7 @@ none true - Bin\Release + bin\Release\ TRACE;SILVERLIGHT;WINDOWS_PHONE true true @@ -48,6 +48,7 @@ + @@ -78,13 +79,11 @@ - - - + diff --git a/MapControl/MapControl.Silverlight.csproj b/MapControl/MapControl.Silverlight.csproj index ed73399e..5b4043e2 100644 --- a/MapControl/MapControl.Silverlight.csproj +++ b/MapControl/MapControl.Silverlight.csproj @@ -75,6 +75,7 @@ + @@ -105,13 +106,11 @@ - - - + diff --git a/MapControl/MapControl.WPF.csproj b/MapControl/MapControl.WPF.csproj index 747fc7b7..28b705fa 100644 --- a/MapControl/MapControl.WPF.csproj +++ b/MapControl/MapControl.WPF.csproj @@ -58,6 +58,7 @@ + @@ -77,7 +78,6 @@ - @@ -86,15 +86,14 @@ + - - - + diff --git a/MapControl/MapImage.cs b/MapControl/MapImage.cs index ae0ed05a..b04a6d4e 100644 --- a/MapControl/MapImage.cs +++ b/MapControl/MapImage.cs @@ -19,7 +19,7 @@ namespace MapControl { public static readonly DependencyProperty SourceProperty = DependencyProperty.Register( "Source", typeof(ImageSource), typeof(MapImage), - new PropertyMetadata(null, (o, e) => ((MapImage)o).SourceChanged((ImageSource)e.NewValue))); + new PropertyMetadata(null, (o, e) => ((MapImage)o).SourcePropertyChanged((ImageSource)e.NewValue))); public ImageSource Source { @@ -27,7 +27,7 @@ namespace MapControl set { SetValue(SourceProperty, value); } } - private void SourceChanged(ImageSource image) + private void SourcePropertyChanged(ImageSource image) { Fill = new ImageBrush { diff --git a/MapControl/MapImageLayer.cs b/MapControl/MapImageLayer.cs index 81ebd005..3205a4f1 100644 --- a/MapControl/MapImageLayer.cs +++ b/MapControl/MapImageLayer.cs @@ -40,7 +40,7 @@ namespace MapControl Children.Add(new MapImage { Opacity = 0d }); Children.Add(new MapImage { Opacity = 0d }); - updateTimer = new DispatcherTimer { Interval = Settings.TileUpdateInterval }; + updateTimer = new DispatcherTimer { Interval = MapBase.TileUpdateInterval }; updateTimer.Tick += (s, e) => UpdateImage(); } @@ -59,7 +59,7 @@ namespace MapControl /// /// Relative size of the map images in relation to the current viewport size. /// Setting a value greater than one will let MapImageLayer request images that - /// are larger than the viewport, in order to support smooth panning. + /// are larger than the viewport, in order to support smooth panning. /// public double RelativeImageSize { @@ -175,16 +175,16 @@ namespace MapControl if (topImage.Source != null) { topImage.BeginAnimation(UIElement.OpacityProperty, - new DoubleAnimation { To = 1d, Duration = Settings.TileAnimationDuration }); + new DoubleAnimation { To = 1d, Duration = Tile.OpacityAnimationDuration }); } if (bottomImage.Source != null) { - var fadeOutAnimation = new DoubleAnimation { To = 0d, Duration = Settings.TileAnimationDuration }; + var fadeOutAnimation = new DoubleAnimation { To = 0d, Duration = Tile.OpacityAnimationDuration }; if (topImage.Source != null) { - fadeOutAnimation.BeginTime = Settings.TileAnimationDuration; + fadeOutAnimation.BeginTime = Tile.OpacityAnimationDuration; } bottomImage.BeginAnimation(UIElement.OpacityProperty, fadeOutAnimation); diff --git a/MapControl/MapItemsControl.WPF.cs b/MapControl/MapItemsControl.WPF.cs index af165538..e368d87c 100644 --- a/MapControl/MapItemsControl.WPF.cs +++ b/MapControl/MapItemsControl.WPF.cs @@ -3,12 +3,9 @@ // Licensed under the Microsoft Public License (Ms-PL) using System; -using System.Collections.Generic; using System.ComponentModel; -using System.Linq; using System.Windows; using System.Windows.Controls; -using System.Windows.Media; namespace MapControl { @@ -17,10 +14,6 @@ namespace MapControl /// public class MapItemsControl : ListBox { - public static readonly DependencyProperty SelectionGeometryProperty = DependencyProperty.Register( - "SelectionGeometry", typeof(Geometry), typeof(MapItemsControl), - new PropertyMetadata((o, e) => ((MapItemsControl)o).SelectionGeometryPropertyChanged((Geometry)e.NewValue))); - static MapItemsControl() { DefaultStyleKeyProperty.OverrideMetadata( @@ -43,58 +36,6 @@ namespace MapControl return item is MapItem; } - /// - /// Gets or sets a Geometry that selects all items inside its fill area, i.e. - /// where Geometry.FillContains returns true for the item's viewport position. - /// - public Geometry SelectionGeometry - { - get { return (Geometry)GetValue(SelectionGeometryProperty); } - set { SetValue(SelectionGeometryProperty, value); } - } - - public object GetFirstItemInGeometry(Geometry geometry) - { - if (geometry == null || geometry.IsEmpty()) - { - return null; - } - - return Items.Cast().FirstOrDefault(i => IsItemInGeometry(i, geometry)); - } - - public IList GetItemsInGeometry(Geometry geometry) - { - if (geometry == null || geometry.IsEmpty()) - { - return null; - } - - return Items.Cast().Where(i => IsItemInGeometry(i, geometry)).ToList(); - } - - private bool IsItemInGeometry(object item, Geometry geometry) - { - var container = ItemContainerGenerator.ContainerFromItem(item) as UIElement; - Point? viewportPosition; - - return container != null && - (viewportPosition = MapPanel.GetViewportPosition(container)).HasValue && - geometry.FillContains(viewportPosition.Value); - } - - private void SelectionGeometryPropertyChanged(Geometry geometry) - { - if (SelectionMode == SelectionMode.Single) - { - SelectedItem = GetFirstItemInGeometry(geometry); - } - else - { - SetSelectedItems(GetItemsInGeometry(geometry)); - } - } - private void CurrentItemChanging(object sender, CurrentChangingEventArgs e) { var container = ItemContainerGenerator.ContainerFromItem(Items.CurrentItem) as UIElement; diff --git a/MapControl/MapPanel.Silverlight.WinRT.cs b/MapControl/MapPanel.Silverlight.WinRT.cs index 3f261587..dc6b0352 100644 --- a/MapControl/MapPanel.Silverlight.WinRT.cs +++ b/MapControl/MapPanel.Silverlight.WinRT.cs @@ -4,11 +4,9 @@ #if WINDOWS_RUNTIME using Windows.UI.Xaml; -using Windows.UI.Xaml.Controls; using Windows.UI.Xaml.Media; #else using System.Windows; -using System.Windows.Controls; using System.Windows.Media; #endif @@ -21,7 +19,11 @@ namespace MapControl public MapPanel() { - if (!(this is MapBase)) + if (this is MapBase) + { + SetValue(ParentMapProperty, this); + } + else { AddParentMapHandlers(this); } @@ -67,10 +69,5 @@ namespace MapControl return parentMap; } - - internal void SetParentMap() - { - SetValue(ParentMapProperty, this); - } } } diff --git a/MapControl/MapPanel.WPF.cs b/MapControl/MapPanel.WPF.cs index 7259136f..b49645f0 100644 --- a/MapControl/MapPanel.WPF.cs +++ b/MapControl/MapPanel.WPF.cs @@ -14,14 +14,17 @@ namespace MapControl public static readonly DependencyProperty ParentMapProperty = ParentMapPropertyKey.DependencyProperty; + public MapPanel() + { + if (this is MapBase) + { + SetValue(ParentMapPropertyKey, this); + } + } + public static MapBase GetParentMap(UIElement element) { return (MapBase)element.GetValue(ParentMapProperty); } - - internal void SetParentMap() - { - SetValue(ParentMapPropertyKey, this); - } } } diff --git a/MapControl/MapPanel.cs b/MapControl/MapPanel.cs index 42a8b580..00699a1a 100644 --- a/MapControl/MapPanel.cs +++ b/MapControl/MapPanel.cs @@ -24,9 +24,6 @@ namespace MapControl public static readonly DependencyProperty LocationProperty = DependencyProperty.RegisterAttached( "Location", typeof(Location), typeof(MapPanel), new PropertyMetadata(null, LocationPropertyChanged)); - public static readonly DependencyProperty ViewportPositionProperty = DependencyProperty.RegisterAttached( - "ViewportPosition", typeof(Point?), typeof(MapPanel), null); - public static Location GetLocation(UIElement element) { return (Location)element.GetValue(LocationProperty); @@ -37,21 +34,12 @@ namespace MapControl element.SetValue(LocationProperty, value); } - public static Point? GetViewportPosition(UIElement element) - { - return (Point?)element.GetValue(ViewportPositionProperty); - } - private MapBase parentMap; public MapBase ParentMap { get { return parentMap; } - } - - void IMapElement.SetParentMap(MapBase map) - { - SetParentMapOverride(map); + set { SetParentMapOverride(value); } } protected virtual void SetParentMapOverride(MapBase map) @@ -114,7 +102,7 @@ namespace MapControl if (mapElement != null) { - mapElement.SetParentMap(e.NewValue as MapBase); + mapElement.ParentMap = e.NewValue as MapBase; } } @@ -149,12 +137,10 @@ namespace MapControl { var mapPosition = parentMap.MapTransform.Transform(location, parentMap.Center.Longitude); // nearest to center longitude viewportPosition = parentMap.ViewportTransform.Transform(mapPosition); - element.SetValue(ViewportPositionProperty, viewportPosition); } else { viewportPosition = new Point(); - element.ClearValue(ViewportPositionProperty); } var translateTransform = element.RenderTransform as TranslateTransform; @@ -189,7 +175,7 @@ namespace MapControl private static void ArrangeElementWithLocation(UIElement element) { - var rect = new Rect(0d, 0d, element.DesiredSize.Width, element.DesiredSize.Height); + var rect = new Rect(new Point(), element.DesiredSize); var frameworkElement = element as FrameworkElement; if (frameworkElement != null) @@ -228,7 +214,7 @@ namespace MapControl private static void ArrangeElementWithoutLocation(UIElement element, Size parentSize) { - var rect = new Rect(0d, 0d, element.DesiredSize.Width, element.DesiredSize.Height); + var rect = new Rect(new Point(), element.DesiredSize); var frameworkElement = element as FrameworkElement; if (frameworkElement != null) diff --git a/MapControl/MapPath.cs b/MapControl/MapPath.cs index a25a5d28..ec59aea4 100644 --- a/MapControl/MapPath.cs +++ b/MapControl/MapPath.cs @@ -20,12 +20,11 @@ namespace MapControl public MapBase ParentMap { get { return parentMap; } - } - - void IMapElement.SetParentMap(MapBase map) - { - parentMap = map; - UpdateData(); + set + { + parentMap = value; + UpdateData(); + } } protected virtual void UpdateData() diff --git a/MapControl/MapPolyline.Silverlight.WinRT.cs b/MapControl/MapPolyline.Silverlight.WinRT.cs index d921ec58..65a85d32 100644 --- a/MapControl/MapPolyline.Silverlight.WinRT.cs +++ b/MapControl/MapPolyline.Silverlight.WinRT.cs @@ -7,7 +7,6 @@ using System.Linq; using Windows.UI.Xaml.Media; #else using System.Windows.Media; - #endif namespace MapControl diff --git a/MapControl/MapRectangle.cs b/MapControl/MapRectangle.cs index cf244559..39e21eff 100644 --- a/MapControl/MapRectangle.cs +++ b/MapControl/MapRectangle.cs @@ -7,7 +7,6 @@ using Windows.Foundation; using Windows.UI.Xaml; using Windows.UI.Xaml.Media; #else -using System; using System.Windows; using System.Windows.Media; #endif @@ -82,10 +81,9 @@ namespace MapControl !double.IsNaN(West) && !double.IsNaN(East) && South < North && West < East) { - var p1 = ParentMap.MapTransform.Transform(new Location(South, West)); - var p2 = ParentMap.MapTransform.Transform(new Location(North, East)); - - SetRect(new Rect(p1.X, p1.Y, p2.X - p1.X, p2.Y - p1.Y)); + SetRect(new Rect( + ParentMap.MapTransform.Transform(new Location(South, West)), + ParentMap.MapTransform.Transform(new Location(North, East)))); } else { diff --git a/MapControl/PanelBase.cs b/MapControl/PanelBase.cs index b895a4d6..e73efd0d 100644 --- a/MapControl/PanelBase.cs +++ b/MapControl/PanelBase.cs @@ -14,7 +14,7 @@ using System.Windows.Controls; namespace MapControl { /// - /// Common base class for MapPanel, TileLayer and TileContainer. + /// Common base class for MapPanel and TileLayer. /// public class PanelBase : Panel { @@ -32,9 +32,9 @@ namespace MapControl protected override Size ArrangeOverride(Size finalSize) { - foreach (UIElement child in Children) + foreach (UIElement element in Children) { - child.Arrange(new Rect(new Point(), finalSize)); + element.Arrange(new Rect(new Point(), finalSize)); } return finalSize; diff --git a/MapControl/Properties/AssemblyInfo.cs b/MapControl/Properties/AssemblyInfo.cs index a997374a..b1552621 100644 --- a/MapControl/Properties/AssemblyInfo.cs +++ b/MapControl/Properties/AssemblyInfo.cs @@ -17,8 +17,8 @@ using System.Windows; [assembly: AssemblyCompany("Clemens Fischer")] [assembly: AssemblyCopyright("Copyright © 2014 Clemens Fischer")] [assembly: AssemblyTrademark("")] -[assembly: AssemblyVersion("2.3.1")] -[assembly: AssemblyFileVersion("2.3.1")] +[assembly: AssemblyVersion("2.4.0")] +[assembly: AssemblyFileVersion("2.4.0")] [assembly: AssemblyConfiguration("")] [assembly: AssemblyCulture("")] [assembly: ComVisible(false)] diff --git a/MapControl/Settings.cs b/MapControl/Settings.cs deleted file mode 100644 index 0938f846..00000000 --- a/MapControl/Settings.cs +++ /dev/null @@ -1,32 +0,0 @@ -// XAML Map Control - http://xamlmapcontrol.codeplex.com/ -// Copyright © 2014 Clemens Fischer -// Licensed under the Microsoft Public License (Ms-PL) - -using System; -#if WINDOWS_RUNTIME -using Windows.UI.Xaml.Media.Animation; -#else -using System.Windows.Media.Animation; -#endif - -namespace MapControl -{ - /// - /// Stores global static properties that control the behaviour of the map control. - /// - public static class Settings - { - public static TimeSpan TileUpdateInterval { get; set; } - public static TimeSpan TileAnimationDuration { get; set; } - public static TimeSpan MapAnimationDuration { get; set; } - public static EasingFunctionBase MapAnimationEasingFunction { get; set; } - - static Settings() - { - TileUpdateInterval = TimeSpan.FromSeconds(0.5); - TileAnimationDuration = TimeSpan.FromSeconds(0.3); - MapAnimationDuration = TimeSpan.FromSeconds(0.3); - MapAnimationEasingFunction = new QuadraticEase { EasingMode = EasingMode.EaseOut }; - } - } -} diff --git a/MapControl/Themes/Generic.xaml b/MapControl/Themes/Generic.xaml index 526596ba..9bb30526 100644 --- a/MapControl/Themes/Generic.xaml +++ b/MapControl/Themes/Generic.xaml @@ -1,4 +1,4 @@ -