Squashed 'FSI.Lib/' content from commit 6aa4846
git-subtree-dir: FSI.Lib git-subtree-split: 6aa48465a834a7bfdd9cbeae8d2e4f769d0c0ff8
This commit is contained in:
78
FSI.Lib/DeEncryptString/DeEncrypt.cs
Normal file
78
FSI.Lib/DeEncryptString/DeEncrypt.cs
Normal file
@@ -0,0 +1,78 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Security.Cryptography;
|
||||
|
||||
namespace FSI.Lib.DeEncryptString
|
||||
{
|
||||
public static class DeEncrypt
|
||||
{
|
||||
/// <summary>
|
||||
/// Encrypts the string.
|
||||
/// </summary>
|
||||
/// <param name="clearText">The clear text.</param>
|
||||
/// <param name="Key">The key.</param>
|
||||
/// <param name="IV">The IV.</param>
|
||||
/// <returns></returns>
|
||||
private static byte[] CryptString(byte[] clearText, byte[] Key, byte[] IV)
|
||||
{
|
||||
MemoryStream ms = new MemoryStream();
|
||||
Rijndael alg = Rijndael.Create();
|
||||
alg.Key = Key;
|
||||
alg.IV = IV;
|
||||
CryptoStream cs = new CryptoStream(ms, alg.CreateEncryptor(), CryptoStreamMode.Write);
|
||||
cs.Write(clearText, 0, clearText.Length);
|
||||
cs.Close();
|
||||
byte[] encryptedData = ms.ToArray();
|
||||
return encryptedData;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Encrypts the string.
|
||||
/// </summary>
|
||||
/// <param name="clearText">The clear text.</param>
|
||||
/// <param name="Password">The password.</param>
|
||||
/// <returns></returns>
|
||||
public static string CryptString(string clearText, string Password)
|
||||
{
|
||||
byte[] clearBytes = System.Text.Encoding.Unicode.GetBytes(clearText);
|
||||
PasswordDeriveBytes pdb = new PasswordDeriveBytes(Password, new byte[] { 0x49, 0x76, 0x61, 0x6e, 0x20, 0x4d, 0x65, 0x64, 0x76, 0x65, 0x64, 0x65, 0x76 });
|
||||
byte[] encryptedData = CryptString(clearBytes, pdb.GetBytes(32), pdb.GetBytes(16));
|
||||
return Convert.ToBase64String(encryptedData);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Decrypts the string.
|
||||
/// </summary>
|
||||
/// <param name="cipherData">The cipher data.</param>
|
||||
/// <param name="Key">The key.</param>
|
||||
/// <param name="IV">The IV.</param>
|
||||
/// <returns></returns>
|
||||
private static byte[] DecryptString(byte[] cipherData, byte[] Key, byte[] IV)
|
||||
{
|
||||
MemoryStream ms = new MemoryStream();
|
||||
var alg = Rijndael.Create();
|
||||
alg.Key = Key;
|
||||
alg.IV = IV;
|
||||
CryptoStream cs = new CryptoStream(ms, alg.CreateDecryptor(), CryptoStreamMode.Write);
|
||||
cs.Write(cipherData, 0, cipherData.Length);
|
||||
cs.Close();
|
||||
byte[] decryptedData = ms.ToArray();
|
||||
return decryptedData;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Decrypts the string.
|
||||
/// </summary>
|
||||
/// <param name="cipherText">The cipher text.</param>
|
||||
/// <param name="Password">The password.</param>
|
||||
/// <returns></returns>
|
||||
public static string DecryptString(string cipherText, string Password)
|
||||
{
|
||||
byte[] cipherBytes = Convert.FromBase64String(cipherText);
|
||||
PasswordDeriveBytes pdb = new PasswordDeriveBytes(Password, new byte[] { 0x49, 0x76, 0x61, 0x6e, 0x20, 0x4d, 0x65, 0x64, 0x76, 0x65, 0x64, 0x65, 0x76 });
|
||||
byte[] decryptedData = DecryptString(cipherBytes, pdb.GetBytes(32), pdb.GetBytes(16));
|
||||
return System.Text.Encoding.Unicode.GetString(decryptedData);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
704
FSI.Lib/EasyEncryption/Encryption.cs
Normal file
704
FSI.Lib/EasyEncryption/Encryption.cs
Normal file
@@ -0,0 +1,704 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Reflection;
|
||||
using System.Security.Cryptography;
|
||||
|
||||
namespace FSI.Lib.EasyEncryption
|
||||
{
|
||||
/// <summary>
|
||||
/// Encryption algorithm types.
|
||||
/// </summary>
|
||||
public enum EncryptionAlgorithm
|
||||
{
|
||||
/// <summary>
|
||||
/// Specifies the Advanced Encryption Standard (AES) symmetric encryption algorithm.
|
||||
/// </summary>
|
||||
Aes,
|
||||
|
||||
/// <summary>
|
||||
/// Specifies the Data Encryption Standard (DES) symmetric encryption algorithm.
|
||||
/// </summary>
|
||||
Des,
|
||||
|
||||
/// <summary>
|
||||
/// Specifies the RC2 symmetric encryption algorithm.
|
||||
/// </summary>
|
||||
Rc2,
|
||||
|
||||
/// <summary>
|
||||
/// Specifies the Rijndael symmetric encryption algorithm.
|
||||
/// </summary>
|
||||
Rijndael,
|
||||
|
||||
/// <summary>
|
||||
/// Specifies the TripleDES symmetric encryption algorithm.
|
||||
/// </summary>
|
||||
TripleDes
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Class to provide encryption and decryption services.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>
|
||||
/// The Encryption class performs encryption and decryption using the specified algorithm.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// The encrypted values may appear considerably larger than the decrypted values
|
||||
/// because encrypted values contains some additional meta data. If either data size or
|
||||
/// performance is a concern, use the <see cref="CreateStreamReader(Stream)"/> or
|
||||
/// <see cref="CreateStreamWriter(Stream)"/> methods to work with the streaming classes
|
||||
/// instead.
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
public class Encryption
|
||||
{
|
||||
private static readonly int SaltLength = 8;
|
||||
|
||||
private static readonly Dictionary<EncryptionAlgorithm, Type> AlgorithmLookup = new Dictionary<EncryptionAlgorithm, Type>
|
||||
{
|
||||
[EncryptionAlgorithm.Aes] = typeof(Aes),
|
||||
[EncryptionAlgorithm.Des] = typeof(DES),
|
||||
[EncryptionAlgorithm.Rc2] = typeof(RC2),
|
||||
[EncryptionAlgorithm.Rijndael] = typeof(Rijndael),
|
||||
[EncryptionAlgorithm.TripleDes] = typeof(TripleDES),
|
||||
};
|
||||
|
||||
private string Password { get; }
|
||||
private Type AlgorithmType { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Converts a byte array to a string.
|
||||
/// </summary>
|
||||
/// <param name="bytes">The byte array to be converted.</param>
|
||||
/// <returns>Returns the converted string.</returns>
|
||||
public static string EncodeBytesToString(byte[] bytes) => Convert.ToBase64String(bytes);
|
||||
|
||||
/// <summary>
|
||||
/// Converts a string to a byte array.
|
||||
/// </summary>
|
||||
/// <param name="s">The string to be converted.</param>
|
||||
/// <returns>Returns the converted byte array.</returns>
|
||||
public static byte[] DecodeBytesFromString(string s) => Convert.FromBase64String(s);
|
||||
|
||||
/// <summary>
|
||||
/// Constructs a new <c>Encryption</c> instance.
|
||||
/// </summary>
|
||||
/// <param name="password">Specifies the encryption password. Leading and trailing spaces are removed.</param>
|
||||
/// <param name="algorithm">Specifies which encryption algorithm is used.</param>
|
||||
public Encryption(string password, EncryptionAlgorithm algorithm)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(password))
|
||||
throw new ArgumentException("Password is required.", nameof(password));
|
||||
Password = password.Trim();
|
||||
AlgorithmType = AlgorithmLookup[algorithm];
|
||||
}
|
||||
|
||||
#region Encryption stream creation methods
|
||||
|
||||
/// <summary>
|
||||
/// Creates an <see cref="EncryptionWriter"/> instance using the specified stream.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// The <see cref="EncryptionWriter"/> class is the preferred method to encrypt data
|
||||
/// as it will store some necessary meta data only once for all data in the stream.
|
||||
/// It is also more performant. The other encryption methods defer to the EncryptionWriter
|
||||
/// class for actual encryption.
|
||||
/// </remarks>
|
||||
/// <param name="stream">The stream the encrypted data will be written to.</param>
|
||||
/// <returns>An instance of the <see cref="EncryptionWriter"/> class.</returns>
|
||||
public EncryptionWriter CreateStreamWriter(Stream stream)
|
||||
{
|
||||
// Create a random salt and write to stream
|
||||
byte[] salt = CreateSalt();
|
||||
stream.Write(salt, 0, salt.Length);
|
||||
// Create symmetric algorithm
|
||||
SymmetricAlgorithm algorithm = CreateAlgorithm();
|
||||
algorithm.Padding = PaddingMode.PKCS7;
|
||||
// Create key and IV
|
||||
byte[] key, iv;
|
||||
GenerateKeyAndIv(algorithm, salt, out key, out iv);
|
||||
// Create EncryptionWriter
|
||||
ICryptoTransform encryptor = algorithm.CreateEncryptor(key, iv);
|
||||
CryptoStream cs = new CryptoStream(stream, encryptor, CryptoStreamMode.Write);
|
||||
return new EncryptionWriter(algorithm, encryptor, stream);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates an <see cref="EncryptionWriter"/> instance using the specified file name.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// The <see cref="EncryptionWriter"/> class is the preferred method to encrypt data
|
||||
/// as it will store some necessary meta data only once for all data in the stream.
|
||||
/// It is also more performant. The other encryption methods defer to the EncryptionWriter
|
||||
/// class for actual encryption.
|
||||
/// </remarks>
|
||||
/// <param name="path">The file name the encrypted data will be written to.</param>
|
||||
/// <returns>An instance of the <see cref="EncryptionWriter"/> class.</returns>
|
||||
public EncryptionWriter CreateStreamWriter(string path)
|
||||
{
|
||||
return CreateStreamWriter(File.Open(path, FileMode.OpenOrCreate, FileAccess.Write));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates an <see cref="EncryptionReader"/> instance using the specified stream.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// The <see cref="EncryptionReader"/> class is the preferred method to decrypt data.
|
||||
/// It is also more performant. The other decryption methods defer to the EncryptionReader
|
||||
/// class for actual decryption.
|
||||
/// </remarks>
|
||||
/// <param name="stream">The stream the encrypted data will be read from.</param>
|
||||
/// <returns>An instance of the <see cref="EncryptionReader"/> class.</returns>
|
||||
public EncryptionReader CreateStreamReader(Stream stream)
|
||||
{
|
||||
// Read salt from input stream
|
||||
byte[] salt = new byte[SaltLength];
|
||||
if (stream.Read(salt, 0, salt.Length) < SaltLength)
|
||||
throw new ArgumentOutOfRangeException("Reached end of input stream before reading encryption metadata.");
|
||||
// Create symmetric algorithm
|
||||
SymmetricAlgorithm algorithm = CreateAlgorithm();
|
||||
algorithm.Padding = PaddingMode.PKCS7;
|
||||
// Create key and IV
|
||||
byte[] key, iv;
|
||||
GenerateKeyAndIv(algorithm, salt, out key, out iv);
|
||||
// Create EncryptionReader
|
||||
ICryptoTransform decryptor = algorithm.CreateDecryptor(key, iv);
|
||||
CryptoStream cs = new CryptoStream(stream, decryptor, CryptoStreamMode.Read);
|
||||
return new EncryptionReader(algorithm, decryptor, stream);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates an <see cref="EncryptionReader"/> instance using the specified file name.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// The <see cref="EncryptionReader"/> class is the preferred method to decrypt data.
|
||||
/// It is also more performant. The other decryption methods defer to the EncryptionReader
|
||||
/// class for actual decryption.
|
||||
/// </remarks>
|
||||
/// <param name="path">The file name the encrypted data will be read from.</param>
|
||||
/// <returns>An instance of the <see cref="EncryptionReader"/> class.</returns>
|
||||
public EncryptionReader CreateStreamReader(string path)
|
||||
{
|
||||
return CreateStreamReader(File.Open(path, FileMode.Open, FileAccess.Read));
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Encryption
|
||||
|
||||
/// <summary>
|
||||
/// Encrypts a <c>string</c> value.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// The encrypted value will be larger than the unencrypted value because the
|
||||
/// encrypted value will contain some additional meta data. To minimize the
|
||||
/// size of this meta data when encrypting multiple values, use <see cref="CreateStreamWriter(Stream)"/>
|
||||
/// to create a <see cref="EncryptionWriter"/> instead.
|
||||
/// </remarks>
|
||||
/// <param name="value">The value to decrypt.</param>
|
||||
/// <returns>Returns the encrypted value</returns>
|
||||
public string Encrypt(string value) => Encrypt(w => w.Write(value ?? string.Empty));
|
||||
|
||||
/// <summary>
|
||||
/// Encrypts a <c>bool</c> value.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// The encrypted value will be larger than the unencrypted value because the
|
||||
/// encrypted value will contain some additional meta data. To minimize the
|
||||
/// size of this meta data when encrypting multiple values, use <see cref="CreateStreamWriter(Stream)"/>
|
||||
/// to create a <see cref="EncryptionWriter"/> instead.
|
||||
/// </remarks>
|
||||
/// <param name="value">The value to decrypt.</param>
|
||||
/// <returns>Returns the encrypted value</returns>
|
||||
public string Encrypt(bool value) => Encrypt(w => w.Write(value));
|
||||
|
||||
/// <summary>
|
||||
/// Encrypts a <c>char</c> value.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// The encrypted value will be larger than the unencrypted value because the
|
||||
/// encrypted value will contain some additional meta data. To minimize the
|
||||
/// size of this meta data when encrypting multiple values, use <see cref="CreateStreamWriter(Stream)"/>
|
||||
/// to create a <see cref="EncryptionWriter"/> instead.
|
||||
/// </remarks>
|
||||
/// <param name="value">The value to decrypt.</param>
|
||||
/// <returns>Returns the encrypted value</returns>
|
||||
public string Encrypt(char value) => Encrypt(w => w.Write(value));
|
||||
|
||||
/// <summary>
|
||||
/// Encrypts a <c>sbyte</c> value.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// The encrypted value will be larger than the unencrypted value because the
|
||||
/// encrypted value will contain some additional meta data. To minimize the
|
||||
/// size of this meta data when encrypting multiple values, use <see cref="CreateStreamWriter(Stream)"/>
|
||||
/// to create a <see cref="EncryptionWriter"/> instead.
|
||||
/// </remarks>
|
||||
/// <param name="value">The value to decrypt.</param>
|
||||
/// <returns>Returns the encrypted value</returns>
|
||||
public string Encrypt(sbyte value) => Encrypt(w => w.Write(value));
|
||||
|
||||
/// <summary>
|
||||
/// Encrypts a <c>byte</c> value.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// The encrypted value will be larger than the unencrypted value because the
|
||||
/// encrypted value will contain some additional meta data. To minimize the
|
||||
/// size of this meta data when encrypting multiple values, use <see cref="CreateStreamWriter(Stream)"/>
|
||||
/// to create a <see cref="EncryptionWriter"/> instead.
|
||||
/// </remarks>
|
||||
/// <param name="value">The value to decrypt.</param>
|
||||
/// <returns>Returns the encrypted value</returns>
|
||||
public string Encrypt(byte value) => Encrypt(w => w.Write(value));
|
||||
|
||||
/// <summary>
|
||||
/// Encrypts a <c>short</c> value.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// The encrypted value will be larger than the unencrypted value because the
|
||||
/// encrypted value will contain some additional meta data. To minimize the
|
||||
/// size of this meta data when encrypting multiple values, use <see cref="CreateStreamWriter(Stream)"/>
|
||||
/// to create a <see cref="EncryptionWriter"/> instead.
|
||||
/// </remarks>
|
||||
/// <param name="value">The value to decrypt.</param>
|
||||
/// <returns>Returns the encrypted value</returns>
|
||||
public string Encrypt(short value) => Encrypt(w => w.Write(value));
|
||||
|
||||
/// <summary>
|
||||
/// Encrypts a <c>ushort</c> value.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// The encrypted value will be larger than the unencrypted value because the
|
||||
/// encrypted value will contain some additional meta data. To minimize the
|
||||
/// size of this meta data when encrypting multiple values, use <see cref="CreateStreamWriter(Stream)"/>
|
||||
/// to create a <see cref="EncryptionWriter"/> instead.
|
||||
/// </remarks>
|
||||
/// <param name="value">The value to decrypt.</param>
|
||||
/// <returns>Returns the encrypted value</returns>
|
||||
public string Encrypt(ushort value) => Encrypt(w => w.Write(value));
|
||||
|
||||
/// <summary>
|
||||
/// Encrypts a <c>int</c> value.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// The encrypted value will be larger than the unencrypted value because the
|
||||
/// encrypted value will contain some additional meta data. To minimize the
|
||||
/// size of this meta data when encrypting multiple values, use <see cref="CreateStreamWriter(Stream)"/>
|
||||
/// to create a <see cref="EncryptionWriter"/> instead.
|
||||
/// </remarks>
|
||||
/// <param name="value">The value to decrypt.</param>
|
||||
/// <returns>Returns the encrypted value</returns>
|
||||
public string Encrypt(int value) => Encrypt(w => w.Write(value));
|
||||
|
||||
/// <summary>
|
||||
/// Encrypts a <c>uint</c> value.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// The encrypted value will be larger than the unencrypted value because the
|
||||
/// encrypted value will contain some additional meta data. To minimize the
|
||||
/// size of this meta data when encrypting multiple values, use <see cref="CreateStreamWriter(Stream)"/>
|
||||
/// to create a <see cref="EncryptionWriter"/> instead.
|
||||
/// </remarks>
|
||||
/// <param name="value">The value to decrypt.</param>
|
||||
/// <returns>Returns the encrypted value</returns>
|
||||
public string Encrypt(uint value) => Encrypt(w => w.Write(value));
|
||||
|
||||
/// <summary>
|
||||
/// Encrypts a <c>long</c> value.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// The encrypted value will be larger than the unencrypted value because the
|
||||
/// encrypted value will contain some additional meta data. To minimize the
|
||||
/// size of this meta data when encrypting multiple values, use <see cref="CreateStreamWriter(Stream)"/>
|
||||
/// to create a <see cref="EncryptionWriter"/> instead.
|
||||
/// </remarks>
|
||||
/// <param name="value">The value to decrypt.</param>
|
||||
/// <returns>Returns the encrypted value</returns>
|
||||
public string Encrypt(long value) => Encrypt(w => w.Write(value));
|
||||
|
||||
/// <summary>
|
||||
/// Encrypts a <c>ulong</c> value.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// The encrypted value will be larger than the unencrypted value because the
|
||||
/// encrypted value will contain some additional meta data. To minimize the
|
||||
/// size of this meta data when encrypting multiple values, use <see cref="CreateStreamWriter(Stream)"/>
|
||||
/// to create a <see cref="EncryptionWriter"/> instead.
|
||||
/// </remarks>
|
||||
/// <param name="value">The value to decrypt.</param>
|
||||
/// <returns>Returns the encrypted value</returns>
|
||||
public string Encrypt(ulong value) => Encrypt(w => w.Write(value));
|
||||
|
||||
/// <summary>
|
||||
/// Encrypts a <c>float</c> value.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// The encrypted value will be larger than the unencrypted value because the
|
||||
/// encrypted value will contain some additional meta data. To minimize the
|
||||
/// size of this meta data when encrypting multiple values, use <see cref="CreateStreamWriter(Stream)"/>
|
||||
/// to create a <see cref="EncryptionWriter"/> instead.
|
||||
/// </remarks>
|
||||
/// <param name="value">The value to decrypt.</param>
|
||||
/// <returns>Returns the encrypted value</returns>
|
||||
public string Encrypt(float value) => Encrypt(w => w.Write(value));
|
||||
|
||||
/// <summary>
|
||||
/// Encrypts a <c>double</c> value.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// The encrypted value will be larger than the unencrypted value because the
|
||||
/// encrypted value will contain some additional meta data. To minimize the
|
||||
/// size of this meta data when encrypting multiple values, use <see cref="CreateStreamWriter(Stream)"/>
|
||||
/// to create a <see cref="EncryptionWriter"/> instead.
|
||||
/// </remarks>
|
||||
/// <param name="value">The value to decrypt.</param>
|
||||
/// <returns>Returns the encrypted value</returns>
|
||||
public string Encrypt(double value) => Encrypt(w => w.Write(value));
|
||||
|
||||
/// <summary>
|
||||
/// Encrypts a <c>decimal</c> value.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// The encrypted value will be larger than the unencrypted value because the
|
||||
/// encrypted value will contain some additional meta data. To minimize the
|
||||
/// size of this meta data when encrypting multiple values, use <see cref="CreateStreamWriter(Stream)"/>
|
||||
/// to create a <see cref="EncryptionWriter"/> instead.
|
||||
/// </remarks>
|
||||
/// <param name="value">The value to decrypt.</param>
|
||||
/// <returns>Returns the encrypted value</returns>
|
||||
public string Encrypt(decimal value) => Encrypt(w => w.Write(value));
|
||||
|
||||
/// <summary>
|
||||
/// Encrypts a <c>DateTime</c> value.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// The encrypted value will be larger than the unencrypted value because the
|
||||
/// encrypted value will contain some additional meta data. To minimize the
|
||||
/// size of this meta data when encrypting multiple values, use <see cref="CreateStreamWriter(Stream)"/>
|
||||
/// to create a <see cref="EncryptionWriter"/> instead.
|
||||
/// </remarks>
|
||||
/// <param name="value">The value to decrypt.</param>
|
||||
/// <returns>Returns the encrypted value</returns>
|
||||
public string Encrypt(DateTime value) => Encrypt(w => w.Write(value));
|
||||
|
||||
/// <summary>
|
||||
/// Encrypts a <c>byte[]</c> value.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// The encrypted value will be larger than the unencrypted value because the
|
||||
/// encrypted value will contain some additional meta data. To minimize the
|
||||
/// size of this meta data when encrypting multiple values, use <see cref="CreateStreamWriter(Stream)"/>
|
||||
/// to create a <see cref="EncryptionWriter"/> instead.
|
||||
/// </remarks>
|
||||
/// <param name="value">The value to decrypt.</param>
|
||||
/// <returns>Returns the encrypted value</returns>
|
||||
public string Encrypt(byte[] value) => Encrypt(w => w.Write(value));
|
||||
|
||||
/// <summary>
|
||||
/// Encrypts a <c>string[]</c> value.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// The encrypted value will be larger than the unencrypted value because the
|
||||
/// encrypted value will contain some additional meta data. To minimize the
|
||||
/// size of this meta data when encrypting multiple values, use <see cref="CreateStreamWriter(Stream)"/>
|
||||
/// to create a <see cref="EncryptionWriter"/> instead.
|
||||
/// </remarks>
|
||||
/// <param name="value">The value to decrypt.</param>
|
||||
/// <returns>Returns the encrypted value</returns>
|
||||
public string Encrypt(string[] value) => Encrypt(w => w.Write(value));
|
||||
|
||||
private string Encrypt(Action<EncryptionWriter> action)
|
||||
{
|
||||
using (MemoryStream stream = new MemoryStream())
|
||||
using (EncryptionWriter writer = CreateStreamWriter(stream))
|
||||
{
|
||||
action(writer);
|
||||
return EncodeBytesToString(stream.ToArray());
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Decryption
|
||||
|
||||
/// <summary>
|
||||
/// Decrypts a <c>string</c> value encrypted with <see cref="Encrypt(string)"/>.
|
||||
/// </summary>
|
||||
/// <param name="encryptedValue">The value to decrypt.</param>
|
||||
/// <returns>Returns the decrypted value</returns>
|
||||
public string DecryptString(string encryptedValue) => (string)Decrypt(encryptedValue, r => r.ReadString());
|
||||
|
||||
/// <summary>
|
||||
/// Decrypts a <c>bool</c> value encrypted with <see cref="Encrypt(bool)"/>.
|
||||
/// </summary>
|
||||
/// <param name="encryptedValue">The value to decrypt.</param>
|
||||
/// <returns>Returns the decrypted value</returns>
|
||||
public bool DecryptBoolean(string encryptedValue) => (bool)Decrypt(encryptedValue, r => r.ReadBoolean());
|
||||
|
||||
/// <summary>
|
||||
/// Decrypts a <c>char</c> value encrypted with <see cref="Encrypt(char)"/>.
|
||||
/// </summary>
|
||||
/// <param name="encryptedValue">The value to decrypt.</param>
|
||||
/// <returns>Returns the decrypted value</returns>
|
||||
public char DecryptChar(string encryptedValue) => (char)Decrypt(encryptedValue, r => r.ReadChar());
|
||||
|
||||
/// <summary>
|
||||
/// Decrypts a <c>sbyte</c> value encrypted with <see cref="Encrypt(sbyte)"/>.
|
||||
/// </summary>
|
||||
/// <param name="encryptedValue">The value to decrypt.</param>
|
||||
/// <returns>Returns the decrypted value</returns>
|
||||
public sbyte DecryptSByte(string encryptedValue) => (sbyte)Decrypt(encryptedValue, r => r.ReadSByte());
|
||||
|
||||
/// <summary>
|
||||
/// Decrypts a <c>byte</c> value encrypted with <see cref="Encrypt(byte)"/>.
|
||||
/// </summary>
|
||||
/// <param name="encryptedValue">The value to decrypt.</param>
|
||||
/// <returns>Returns the decrypted value</returns>
|
||||
public byte DecryptByte(string encryptedValue) => (byte)Decrypt(encryptedValue, r => r.ReadByte());
|
||||
|
||||
/// <summary>
|
||||
/// Decrypts a <c>short</c> value encrypted with <see cref="Encrypt(short)"/>.
|
||||
/// </summary>
|
||||
/// <param name="encryptedValue">The value to decrypt.</param>
|
||||
/// <returns>Returns the decrypted value</returns>
|
||||
public short DecryptInt16(string encryptedValue) => (short)Decrypt(encryptedValue, r => r.ReadInt16());
|
||||
|
||||
/// <summary>
|
||||
/// Decrypts a <c>ushort</c> value encrypted with <see cref="Encrypt(ushort)"/>.
|
||||
/// </summary>
|
||||
/// <param name="encryptedValue">The value to decrypt.</param>
|
||||
/// <returns>Returns the decrypted value</returns>
|
||||
public ushort DecryptUInt16(string encryptedValue) => (ushort)Decrypt(encryptedValue, r => r.ReadUInt16());
|
||||
|
||||
/// <summary>
|
||||
/// Decrypts a <c>int</c> value encrypted with <see cref="Encrypt(int)"/>.
|
||||
/// </summary>
|
||||
/// <param name="encryptedValue">The value to decrypt.</param>
|
||||
/// <returns>Returns the decrypted value</returns>
|
||||
public int DecryptInt32(string encryptedValue) => (int)Decrypt(encryptedValue, r => r.ReadInt32());
|
||||
|
||||
/// <summary>
|
||||
/// Decrypts a <c>uint</c> value encrypted with <see cref="Encrypt(uint)"/>.
|
||||
/// </summary>
|
||||
/// <param name="encryptedValue">The value to decrypt.</param>
|
||||
/// <returns>Returns the decrypted value</returns>
|
||||
public uint DecryptUInt32(string encryptedValue) => (uint)Decrypt(encryptedValue, r => r.ReadUInt32());
|
||||
|
||||
/// <summary>
|
||||
/// Decrypts a <c>long</c> value encrypted with <see cref="Encrypt(long)"/>.
|
||||
/// </summary>
|
||||
/// <param name="encryptedValue">The value to decrypt.</param>
|
||||
/// <returns>Returns the decrypted value</returns>
|
||||
public long DecryptInt64(string encryptedValue) => (long)Decrypt(encryptedValue, r => r.ReadInt64());
|
||||
|
||||
/// <summary>
|
||||
/// Decrypts a <c>ulong</c> value encrypted with <see cref="Encrypt(ulong)"/>.
|
||||
/// </summary>
|
||||
/// <param name="encryptedValue">The value to decrypt.</param>
|
||||
/// <returns>Returns the decrypted value</returns>
|
||||
public ulong DecryptUInt64(string encryptedValue) => (ulong)Decrypt(encryptedValue, r => r.ReadUInt64());
|
||||
|
||||
/// <summary>
|
||||
/// Decrypts a <c>float</c> value encrypted with <see cref="Encrypt(float)"/>.
|
||||
/// </summary>
|
||||
/// <param name="encryptedValue">The value to decrypt.</param>
|
||||
/// <returns>Returns the decrypted value</returns>
|
||||
public float DecryptSingle(string encryptedValue) => (float)Decrypt(encryptedValue, r => r.ReadSingle());
|
||||
|
||||
/// <summary>
|
||||
/// Decrypts a <c>double</c> value encrypted with <see cref="Encrypt(double)"/>.
|
||||
/// </summary>
|
||||
/// <param name="encryptedValue">The value to decrypt.</param>
|
||||
/// <returns>Returns the decrypted value</returns>
|
||||
public double DecryptDouble(string encryptedValue) => (double)Decrypt(encryptedValue, r => r.ReadDouble());
|
||||
|
||||
/// <summary>
|
||||
/// Decrypts a <c>decimal</c> value encrypted with <see cref="Encrypt(decimal)"/>.
|
||||
/// </summary>
|
||||
/// <param name="encryptedValue">The value to decrypt.</param>
|
||||
/// <returns>Returns the decrypted value</returns>
|
||||
public decimal DecryptDecimal(string encryptedValue) => (decimal)Decrypt(encryptedValue, r => r.ReadDecimal());
|
||||
|
||||
/// <summary>
|
||||
/// Decrypts a <c>DateTime</c> value encrypted with <see cref="Encrypt(DateTime)"/>.
|
||||
/// </summary>
|
||||
/// <param name="encryptedValue">The value to decrypt.</param>
|
||||
/// <returns>Returns the decrypted value</returns>
|
||||
public DateTime DecryptDateTime(string encryptedValue) => (DateTime)Decrypt(encryptedValue, r => r.ReadDateTime());
|
||||
|
||||
/// <summary>
|
||||
/// Decrypts a <c>byte[]</c> value encrypted with <see cref="Encrypt(byte[])"/>.
|
||||
/// </summary>
|
||||
/// <param name="encryptedValue">The value to decrypt.</param>
|
||||
/// <returns>Returns the decrypted value</returns>
|
||||
public byte[] DecryptByteArray(string encryptedValue) => (byte[])Decrypt(encryptedValue, r => r.ReadByteArray());
|
||||
|
||||
/// <summary>
|
||||
/// Decrypts a <c>string[]</c> value encrypted with <see cref="Encrypt(string[])"/>.
|
||||
/// </summary>
|
||||
/// <param name="encryptedValue">The value to decrypt.</param>
|
||||
/// <returns>Returns the decrypted value</returns>
|
||||
public string[] DecryptStringArray(string encryptedValue) => (string[])Decrypt(encryptedValue, r => r.ReadStringArray());
|
||||
|
||||
private object Decrypt(string encryptedValue, Func<EncryptionReader, object> action)
|
||||
{
|
||||
using (MemoryStream stream = new MemoryStream(DecodeBytesFromString(encryptedValue)))
|
||||
using (EncryptionReader reader = CreateStreamReader(stream))
|
||||
{
|
||||
return action(reader);
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Encryption/decryption of objects
|
||||
|
||||
/// <summary>
|
||||
/// Class to hold encrypt/decrypt functions for each supported data type.
|
||||
/// </summary>
|
||||
private class TypeInfo
|
||||
{
|
||||
public Func<Encryption, object, string> Encrypt { get; set; }
|
||||
public Func<Encryption, string, object> Decrypt { get; set; }
|
||||
|
||||
public TypeInfo(Func<Encryption, object, string> encrypt, Func<Encryption, string, object> decrypt)
|
||||
{
|
||||
Encrypt = encrypt;
|
||||
Decrypt = decrypt;
|
||||
}
|
||||
}
|
||||
|
||||
private static readonly Dictionary<Type, TypeInfo> TypeInfoLookup = new Dictionary<Type, TypeInfo>()
|
||||
{
|
||||
[typeof(string)] = new TypeInfo((e, v) => e.Encrypt((string)v), (e, s) => e.DecryptString(s)),
|
||||
[typeof(bool)] = new TypeInfo((e, v) => e.Encrypt((bool)v), (e, s) => e.DecryptBoolean(s)),
|
||||
[typeof(char)] = new TypeInfo((e, v) => e.Encrypt((char)v), (e, s) => e.DecryptChar(s)),
|
||||
[typeof(sbyte)] = new TypeInfo((e, v) => e.Encrypt((sbyte)v), (e, s) => e.DecryptSByte(s)),
|
||||
[typeof(byte)] = new TypeInfo((e, v) => e.Encrypt((byte)v), (e, s) => e.DecryptByte(s)),
|
||||
[typeof(short)] = new TypeInfo((e, v) => e.Encrypt((short)v), (e, s) => e.DecryptInt16(s)),
|
||||
[typeof(ushort)] = new TypeInfo((e, v) => e.Encrypt((ushort)v), (e, s) => e.DecryptUInt16(s)),
|
||||
[typeof(int)] = new TypeInfo((e, v) => e.Encrypt((int)v), (e, s) => e.DecryptInt32(s)),
|
||||
[typeof(uint)] = new TypeInfo((e, v) => e.Encrypt((uint)v), (e, s) => e.DecryptUInt32(s)),
|
||||
[typeof(long)] = new TypeInfo((e, v) => e.Encrypt((long)v), (e, s) => e.DecryptInt64(s)),
|
||||
[typeof(ulong)] = new TypeInfo((e, v) => e.Encrypt((ulong)v), (e, s) => e.DecryptUInt64(s)),
|
||||
[typeof(float)] = new TypeInfo((e, v) => e.Encrypt((float)v), (e, s) => e.DecryptSingle(s)),
|
||||
[typeof(double)] = new TypeInfo((e, v) => e.Encrypt((double)v), (e, s) => e.DecryptDouble(s)),
|
||||
[typeof(decimal)] = new TypeInfo((e, v) => e.Encrypt((decimal)v), (e, s) => e.DecryptDecimal(s)),
|
||||
[typeof(DateTime)] = new TypeInfo((e, v) => e.Encrypt((DateTime)v), (e, s) => e.DecryptDateTime(s)),
|
||||
[typeof(byte[])] = new TypeInfo((e, v) => e.Encrypt((byte[])v), (e, s) => e.DecryptByteArray(s)),
|
||||
[typeof(string[])] = new TypeInfo((e, v) => e.Encrypt((string[])v), (e, s) => e.DecryptStringArray(s)),
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Indicates if the specified data type is supported by the encryption and decryption methods.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// The encryption code supports all basic .NET data types in addition to <c>byte[]</c>
|
||||
/// and <c>string[]</c>. More complex data types are not supported.
|
||||
/// </remarks>
|
||||
/// <param name="type">The data type to be tested.</param>
|
||||
/// <returns>True if the specified type is supported. False otherwise.</returns>
|
||||
public static bool IsTypeSupported(Type type) => TypeInfoLookup.ContainsKey(type);
|
||||
|
||||
/// <summary>
|
||||
/// Encrypts an object value. The object must hold one of the supported data types.
|
||||
/// </summary>
|
||||
/// <param name="value">Object to be encrypted.</param>
|
||||
/// <exception cref="ArgumentException"><paramref name="value"/> holds an unsupported data type.</exception>
|
||||
/// <returns>An encrypted string that can be decrypted using Decrypt.</returns>
|
||||
public string Encrypt(object value)
|
||||
{
|
||||
if (value == null)
|
||||
return null;
|
||||
if (TypeInfoLookup.TryGetValue(value.GetType(), out TypeInfo info))
|
||||
return info.Encrypt(this, value);
|
||||
throw new ArgumentException(string.Format("Cannot encrypt value : Data type '{0}' is not supported", value.GetType()));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Decrypts an object value of the specified type.
|
||||
/// </summary>
|
||||
/// <param name="encryptedValue">The encrypted string to be decrypted.</param>
|
||||
/// <param name="targetType">The type of data that was originally encrypted into <paramref name="encryptedValue"/>.</param>
|
||||
/// <exception cref="ArgumentException"></exception>
|
||||
/// <returns>Returns the decrypted value.</returns>
|
||||
public object Decrypt(string encryptedValue, Type targetType)
|
||||
{
|
||||
if (TypeInfoLookup.TryGetValue(targetType, out TypeInfo info))
|
||||
return info.Decrypt(this, encryptedValue);
|
||||
throw new ArgumentException(string.Format("Cannot decrypt value : Data type '{0}' is not supported", targetType));
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Support methods
|
||||
|
||||
/// <summary>
|
||||
/// Creates a SymmetricAlgorithm instance for the current encryption algorithm.
|
||||
/// </summary>
|
||||
/// <returns>
|
||||
/// Returns the created SymmetricAlgorithm instance.
|
||||
/// </returns>
|
||||
protected SymmetricAlgorithm CreateAlgorithm()
|
||||
{
|
||||
MethodInfo method = AlgorithmType.GetMethod("Create", Array.Empty<Type>());
|
||||
if (method != null)
|
||||
{
|
||||
if (method.Invoke(null, null) is SymmetricAlgorithm algorithm)
|
||||
return algorithm;
|
||||
}
|
||||
throw new Exception($"Unable to create instance of {AlgorithmType.FullName}.");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Generates a salt that contains a cryptographically strong sequence of random values.
|
||||
/// </summary>
|
||||
/// <returns>The generated salt value.</returns>
|
||||
private byte[] CreateSalt()
|
||||
{
|
||||
byte[] salt = new byte[SaltLength];
|
||||
using (RNGCryptoServiceProvider generator = new RNGCryptoServiceProvider())
|
||||
{
|
||||
generator.GetBytes(salt);
|
||||
}
|
||||
return salt;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Generates a pseudorandom key and initialization vector from the current password and the
|
||||
/// given salt.
|
||||
/// </summary>
|
||||
/// <param name="algorithm"><see cref="SymmetricAlgorithm"></see> being used to encrypt.</param>
|
||||
/// <param name="salt">The salt used to derive the key and initialization vector.</param>
|
||||
/// <param name="key">Returns the generated key.</param>
|
||||
/// <param name="iv">Returns the generated initialization vector.</param>
|
||||
protected void GenerateKeyAndIv(SymmetricAlgorithm algorithm, byte[] salt, out byte[] key, out byte[] iv)
|
||||
{
|
||||
int keyLength = algorithm.KeySize >> 3;
|
||||
int ivLength = algorithm.BlockSize >> 3;
|
||||
byte[] bytes = DeriveBytes(salt, keyLength + ivLength);
|
||||
key = new byte[keyLength];
|
||||
Buffer.BlockCopy(bytes, 0, key, 0, keyLength);
|
||||
iv = new byte[ivLength];
|
||||
Buffer.BlockCopy(bytes, keyLength, iv, 0, ivLength);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Generates a series of pseudorandom bytes of the specified length based on the current
|
||||
/// password and the given salt.
|
||||
/// </summary>
|
||||
/// <param name="salt">The salt used to derive the bytes.</param>
|
||||
/// <param name="bytes">The number of bits of data to generate.</param>
|
||||
/// <returns>Returns the derived bytes.</returns>
|
||||
protected byte[] DeriveBytes(byte[] salt, int bytes)
|
||||
{
|
||||
Rfc2898DeriveBytes derivedBytes = new Rfc2898DeriveBytes(Password, salt, 1000);
|
||||
return derivedBytes.GetBytes(bytes);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
}
|
||||
}
|
||||
105
FSI.Lib/EasyEncryption/EncryptionReader.cs
Normal file
105
FSI.Lib/EasyEncryption/EncryptionReader.cs
Normal file
@@ -0,0 +1,105 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Security.Cryptography;
|
||||
|
||||
namespace FSI.Lib.EasyEncryption
|
||||
{
|
||||
/// <summary>
|
||||
/// Class that provides streaming decryption functionality.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Using this class is the preferred way to decrypt values from a file or memory.
|
||||
/// Other decryption methods defer to this class for actual decryption.
|
||||
/// </remarks>
|
||||
public class EncryptionReader : BinaryReader, IDisposable
|
||||
{
|
||||
private SymmetricAlgorithm Algorithm;
|
||||
private ICryptoTransform Decryptor;
|
||||
|
||||
internal EncryptionReader(SymmetricAlgorithm algorithm, ICryptoTransform decryptor, Stream stream) : base(stream)
|
||||
{
|
||||
Algorithm = algorithm;
|
||||
Decryptor = decryptor;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reads a <c>DateTime</c> value from the encrypted stream.
|
||||
/// </summary>
|
||||
/// <returns>The decrypted value.</returns>
|
||||
public DateTime ReadDateTime()
|
||||
{
|
||||
return new DateTime(ReadInt64());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reads a <c>byte[]</c> value from the encrypted stream.
|
||||
/// </summary>
|
||||
/// <returns>The decrypted values.</returns>
|
||||
public byte[] ReadByteArray()
|
||||
{
|
||||
int count = ReadInt32();
|
||||
byte[] bytes = new byte[count];
|
||||
if (count > 0)
|
||||
Read(bytes, 0, count);
|
||||
return bytes;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reads a <c>string[]</c> value from the encrypted stream.
|
||||
/// </summary>
|
||||
/// <returns>The decrypted values.</returns>
|
||||
public string[] ReadStringArray()
|
||||
{
|
||||
int count = ReadInt32();
|
||||
string[] strings = new string[count];
|
||||
for (int i = 0; i < count; i++)
|
||||
strings[i] = ReadString();
|
||||
return strings;
|
||||
}
|
||||
|
||||
#region IDisposable implementation
|
||||
|
||||
private bool disposed = false; // To detect redundant calls
|
||||
|
||||
/// <summary>
|
||||
/// Releases all resources used by the current instance of the <c>EncryptionReader</c> class.
|
||||
/// </summary>
|
||||
public new void Dispose()
|
||||
{
|
||||
Dispose(true);
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Releases the unmanaged resources used by the <c>EncryptionReader</c> class and optionally
|
||||
/// releases the managed resources.
|
||||
/// </summary>
|
||||
/// <param name="disposing"><c>true</c> to release both managed and unmanaged resources;
|
||||
/// <c>false</c> to release only unmanaged resources.</param>
|
||||
protected override void Dispose(bool disposing)
|
||||
{
|
||||
if (!disposed)
|
||||
{
|
||||
disposed = true;
|
||||
if (disposing)
|
||||
{
|
||||
// Dispose managed objects
|
||||
base.Dispose(true);
|
||||
Decryptor.Dispose();
|
||||
Algorithm.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Destructs this instance of <c>EncryptionReader</c>.
|
||||
/// </summary>
|
||||
~EncryptionReader()
|
||||
{
|
||||
Dispose(false);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
}
|
||||
}
|
||||
105
FSI.Lib/EasyEncryption/EncryptionWriter.cs
Normal file
105
FSI.Lib/EasyEncryption/EncryptionWriter.cs
Normal file
@@ -0,0 +1,105 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Security.Cryptography;
|
||||
|
||||
namespace FSI.Lib.EasyEncryption
|
||||
{
|
||||
/// <summary>
|
||||
/// Class that provides streaming encryption functionality.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Using this class is the preferred way to encrypt values to a file or memory.
|
||||
/// Other encryption methods defer to this class for actual encryption. Meta data
|
||||
/// that must be stored with the encrypted result is only stored once for all
|
||||
/// data in the stream.
|
||||
/// </remarks>
|
||||
public class EncryptionWriter : BinaryWriter, IDisposable
|
||||
{
|
||||
private readonly SymmetricAlgorithm Algorithm;
|
||||
private readonly ICryptoTransform Encryptor;
|
||||
|
||||
internal EncryptionWriter(SymmetricAlgorithm algorithm, ICryptoTransform encryptor, Stream stream) : base(stream)
|
||||
{
|
||||
Algorithm = algorithm;
|
||||
Encryptor = encryptor;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Writes a <c>DateTime</c> value to the encrypted stream.
|
||||
/// </summary>
|
||||
/// <param name="value"><c>DateTime</c> value to write.</param>
|
||||
public void Write(DateTime value)
|
||||
{
|
||||
Write(value.Ticks);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Writes a <c>byte</c> array to the encrypted stream.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Note: Hides <c>BinaryWriter.Write(byte[])</c>.
|
||||
/// </remarks>
|
||||
/// <param name="value"><c>byte[]</c> values to write.</param>
|
||||
public new void Write(byte[] value)
|
||||
{
|
||||
Write(value.Length);
|
||||
Write(value, 0, value.Length);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Writes a <c>string</c> to the encrypted stream.
|
||||
/// </summary>
|
||||
/// <param name="value"><c>string[]</c> values to write.</param>
|
||||
public void Write(string[] value)
|
||||
{
|
||||
Write(value.Length);
|
||||
for (int i = 0; i < value.Length; i++)
|
||||
Write(value[i]);
|
||||
}
|
||||
|
||||
#region IDisposable implementation
|
||||
|
||||
private bool disposed = false; // To detect redundant calls
|
||||
|
||||
/// <summary>
|
||||
/// Releases all resources used by the current instance of the <c>EncryptionWriter</c> class.
|
||||
/// </summary>
|
||||
public new void Dispose()
|
||||
{
|
||||
Dispose(true);
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Releases the unmanaged resources used by the <c>EncryptionWriter</c> class and optionally
|
||||
/// releases the managed resources.
|
||||
/// </summary>
|
||||
/// <param name="disposing"><c>true</c> to release both managed and unmanaged resources;
|
||||
/// <c>false</c> to release only unmanaged resources.</param>
|
||||
protected override void Dispose(bool disposing)
|
||||
{
|
||||
if (!disposed)
|
||||
{
|
||||
disposed = true;
|
||||
if (disposing)
|
||||
{
|
||||
// Dispose managed objects
|
||||
base.Dispose(true);
|
||||
Encryptor.Dispose();
|
||||
Algorithm.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Destructs this instance of <c>EncryptionWriter</c>.
|
||||
/// </summary>
|
||||
~EncryptionWriter()
|
||||
{
|
||||
Dispose(false);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
}
|
||||
}
|
||||
90
FSI.Lib/EasyEncryption/Readme.md
Normal file
90
FSI.Lib/EasyEncryption/Readme.md
Normal file
@@ -0,0 +1,90 @@
|
||||
# EasyEncryption
|
||||
|
||||
[](https://www.nuget.org/packages/SoftCircuits.EasyEncryption/)
|
||||
|
||||
```
|
||||
Install-Package SoftCircuits.EasyEncryption
|
||||
```
|
||||
|
||||
The .NET Framework provides a number of encryption routines. However, these routines generally require a bit of work to set up correctly. Use `EasyEncryption` to make these encryption routines more easily accessible.
|
||||
|
||||
## Encrypting a String
|
||||
|
||||
The `Encrypt()` method can be used to encrypt a string. Use `DecryptString()` to decrypt the string back to the original.
|
||||
|
||||
```cs
|
||||
Encryption encrypt = new Encryption("Password123", EncryptionAlgorithm.TripleDes);
|
||||
|
||||
string original = "This is my message";
|
||||
string cipher = encrypt.Encrypt(original);
|
||||
string result = encrypt.DecryptString(cipher);
|
||||
|
||||
Debug.Assert(result == message);
|
||||
```
|
||||
|
||||
## Encrypting other Types
|
||||
|
||||
The `Encrypt()` method is overloaded to encrypt many different data types. When decrypting, you must use the decryption method specific to the data type you are decrypting. This example encrypts an `int` and `double` value.
|
||||
|
||||
```cs
|
||||
Encryption encrypt = new Encryption("Password123", EncryptionAlgorithm.TripleDes);
|
||||
|
||||
int originalInt = 55;
|
||||
double originalDouble = 123.45;
|
||||
string cipherInt = encrypt.Encrypt(originalInt);
|
||||
string cipherDouble = encrypt.Encrypt(originalDouble);
|
||||
int resultInt = encrypt.DecryptInt32(cipherInt);
|
||||
double resultDouble = encrypt.DecryptDouble(cipherDouble);
|
||||
|
||||
Debug.Assert(resultInt == originalInt);
|
||||
Debug.Assert(resultDouble == originalDouble);
|
||||
```
|
||||
|
||||
## Streams
|
||||
|
||||
`EasyEncryption` also provides the streaming classes `EncryptionWriter` and `EncryptionReader`. These classes work well when encrypting to (or decrypting from) files.
|
||||
|
||||
The following example uses the `CreateStreamWriter()` to encrypt a number of integer values to a file.
|
||||
|
||||
```cs
|
||||
Encryption encrypt = new Encryption("Password123", EncryptionAlgorithm.TripleDes);
|
||||
int[] intValues = { 123, 88, 902, 27, 16, 4, 478, 54 };
|
||||
|
||||
using (EncryptionWriter writer = encrypt.CreateStreamWriter(path))
|
||||
{
|
||||
for (int i = 0; i < intValues.Length; i++)
|
||||
writer.Write(intValues[i]);
|
||||
}
|
||||
```
|
||||
|
||||
Use the `CreateStreamReader()` method to decrypt those integer values from the file.
|
||||
|
||||
```cs
|
||||
Encryption encrypt = new Encryption("Password123", EncryptionAlgorithm.TripleDes);
|
||||
int[] intValues = new int[8];
|
||||
|
||||
using (EncryptionReader reader = encrypt.CreateStreamReader(path))
|
||||
{
|
||||
for (int i = 0; i < intValues.Length; i++)
|
||||
intValues[i] = reader.ReadInt32();
|
||||
}
|
||||
```
|
||||
|
||||
Also, the `CreateStreamWriter()` and `CreateStreamReader()` methods are overloaded to accept a stream argument, allowing you to use custom streams. For example, you could use a `MemoryStream` to encrypt data to memory. This is demonstrated in the following example. It also uses the static method `EncodeBytesToString()` method to convert the results to a string. (Note that there is also a corresponding static `DecodeBytesFromString()` method.)
|
||||
|
||||
```cs
|
||||
Encryption encrypt = new Encryption("Password123", EncryptionAlgorithm.TripleDes);
|
||||
|
||||
using (MemoryStream stream = new MemoryStream())
|
||||
using (EncryptionWriter writer = encrypt.CreateStreamWriter(stream))
|
||||
{
|
||||
writer.Write("ABC");
|
||||
writer.Write(123);
|
||||
writer.Write(123.45);
|
||||
string s = Encryption.EncodeBytesToString(stream.ToArray());
|
||||
}
|
||||
```
|
||||
|
||||
Note that the streaming classes are actually the most efficient way to encrypt and decrypt data. In fact the `Encrypt()` and decryption methods create an instance of `EncryptionWriter` internally (using a `MemoryStream`), even when only encrypting or decrypting a single byte.
|
||||
|
||||
In addition, it should be pointed out that the encrypted results produced by these routines include embedded meta data, making the encrypted data slightly larger than it would otherwise be. However, when encrypting to a stream, this data would only be stored once regardless of the number of values added to the stream. The takeaway is that you can use the `Encrypt()` method for a simple encryption, but should use the streaming classes for anything more complex.
|
||||
34
FSI.Lib/FSI.Lib.csproj
Normal file
34
FSI.Lib/FSI.Lib.csproj
Normal file
@@ -0,0 +1,34 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFrameworks>net472;net6.0-windows</TargetFrameworks>
|
||||
<OutputType>Library</OutputType>
|
||||
<GenerateAssemblyInfo>true</GenerateAssemblyInfo>
|
||||
<UseWPF>true</UseWPF>
|
||||
<ImportWindowsDesktopTargets>true</ImportWindowsDesktopTargets>
|
||||
<AssemblyVersion>3.0</AssemblyVersion>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<None Remove="Icons\Cross.png" />
|
||||
<None Remove="Icons\FondiumU.ico" />
|
||||
<None Remove="Icons\Open.png" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<AppDesigner Include="Properties\" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Resource Include="Icons\Cross.png" />
|
||||
<Resource Include="Icons\FondiumU.ico" />
|
||||
<Resource Include="Icons\Open.png" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<None Include="Guis\SieStarterCsvExporter\Convert_SINAMICS_trace_CSV.exe">
|
||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||
</None>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="System.Configuration.ConfigurationManager" Version="6.0.0" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
54
FSI.Lib/Guis/AutoPw/FrmMain.xaml
Normal file
54
FSI.Lib/Guis/AutoPw/FrmMain.xaml
Normal file
@@ -0,0 +1,54 @@
|
||||
<Window x:Class="FSI.Lib.Guis.AutoPw.FrmMain"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:local="clr-namespace:FSI.Lib.Guis.AutoPw"
|
||||
mc:Ignorable="d"
|
||||
Title="Passwort eingeben"
|
||||
SizeToContent="WidthAndHeight"
|
||||
Height="Auto"
|
||||
Width="Auto"
|
||||
Icon="../../Icons/FondiumU.ico"
|
||||
WindowStyle="ToolWindow">
|
||||
|
||||
<Grid FocusManager.FocusedElement="{Binding ElementName=TbPw}">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="*" />
|
||||
<RowDefinition Height="*" />
|
||||
<RowDefinition Height="Auto" />
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<TextBlock Text="Passwort:"
|
||||
Margin="5 5" />
|
||||
<PasswordBox x:Name="TbPw"
|
||||
Margin=" 5 5 5 5"
|
||||
MinWidth="150"
|
||||
PasswordChanged="TbPw_PasswordChanged"
|
||||
KeyDown="TbPw_KeyDown"/>
|
||||
</StackPanel>
|
||||
|
||||
<StackPanel Grid.Row="1"
|
||||
Orientation="Horizontal">
|
||||
<Button x:Name="BtnOk"
|
||||
Content="Ok"
|
||||
MinWidth="75"
|
||||
Click="BtnOk_Click"
|
||||
Margin="5 5 " />
|
||||
|
||||
<Button x:Name="BtnCancel"
|
||||
Content="Abbruch"
|
||||
MinWidth="75"
|
||||
Click="BtnCancel_Click"
|
||||
Margin="5 5 " />
|
||||
|
||||
</StackPanel>
|
||||
|
||||
<StatusBar Grid.Row="2">
|
||||
<StatusBarItem HorizontalAlignment="Right">
|
||||
<TextBlock Text="{Binding CurrentTime}" />
|
||||
</StatusBarItem>
|
||||
</StatusBar>
|
||||
</Grid>
|
||||
</Window>
|
||||
79
FSI.Lib/Guis/AutoPw/FrmMain.xaml.cs
Normal file
79
FSI.Lib/Guis/AutoPw/FrmMain.xaml.cs
Normal file
@@ -0,0 +1,79 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Data;
|
||||
using System.Windows.Documents;
|
||||
using System.Windows.Input;
|
||||
using System.Windows.Media;
|
||||
using System.Windows.Media.Imaging;
|
||||
using System.Windows.Shapes;
|
||||
|
||||
namespace FSI.Lib.Guis.AutoPw
|
||||
{
|
||||
/// <summary>
|
||||
/// Interaktionslogik für FrmMain.xaml
|
||||
/// </summary>
|
||||
public partial class FrmMain : Window
|
||||
{
|
||||
public bool CloseAtLostFocus { get; set; }
|
||||
public bool PwOk { get; set; }
|
||||
public FrmMain()
|
||||
{
|
||||
InitializeComponent();
|
||||
|
||||
BtnOk.IsEnabled = false;
|
||||
Deactivated += FrmMain_Deactivated;
|
||||
DataContext = new MVVM.ViewModel.CurrentTimeViewModel();
|
||||
}
|
||||
|
||||
private void FrmMain_Deactivated(object sender, System.EventArgs e)
|
||||
{
|
||||
if (CloseAtLostFocus)
|
||||
Visibility = Visibility.Hidden;
|
||||
}
|
||||
|
||||
private void BtnOk_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
Close();
|
||||
}
|
||||
|
||||
private void BtnCancel_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
Close();
|
||||
PwOk = false;
|
||||
}
|
||||
|
||||
|
||||
private void TbPw_PasswordChanged(object sender, RoutedEventArgs e)
|
||||
{
|
||||
|
||||
if (((PasswordBox)sender).Password.Equals(DateTime.Now.ToString("yyyyMMdd")))
|
||||
{
|
||||
BtnOk.IsEnabled = true;
|
||||
PwOk = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
BtnOk.IsEnabled = false;
|
||||
PwOk = false;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private void TbPw_KeyDown(object sender, KeyEventArgs e)
|
||||
{
|
||||
if (BtnOk.IsEnabled == true)
|
||||
{
|
||||
PwOk = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
PwOk = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
55
FSI.Lib/Guis/DeEncryptMessage/FrmMain.xaml
Normal file
55
FSI.Lib/Guis/DeEncryptMessage/FrmMain.xaml
Normal file
@@ -0,0 +1,55 @@
|
||||
<Window x:Class="FSI.Lib.Guis.DeEncryptMessage.FrmMain"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:local="clr-namespace:FSI.Lib.Guis.DeEncryptMessage"
|
||||
mc:Ignorable="d"
|
||||
Title="FSI Message Ent-/Verschlüsseln"
|
||||
SizeToContent="WidthAndHeight"
|
||||
Height="Auto"
|
||||
Width="Auto"
|
||||
Icon="../../Icons/FondiumU.ico">
|
||||
|
||||
<Grid MinWidth="300">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition MinHeight="100" />
|
||||
<RowDefinition MinHeight="100" />
|
||||
<RowDefinition MaxHeight="30"
|
||||
Height="*" />
|
||||
<RowDefinition Height="Auto" />
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<TextBox x:Name="tbInput"
|
||||
TextWrapping="Wrap"
|
||||
AcceptsReturn="True"
|
||||
HorizontalScrollBarVisibility="Disabled"
|
||||
VerticalScrollBarVisibility="Auto" />
|
||||
|
||||
<TextBox x:Name="tboutput"
|
||||
Grid.Row="1"
|
||||
AcceptsReturn="True"
|
||||
HorizontalScrollBarVisibility="Disabled"
|
||||
VerticalScrollBarVisibility="Auto" />
|
||||
|
||||
<StackPanel Orientation="Horizontal"
|
||||
Grid.Row="2">
|
||||
<Button x:Name="btnCrypt"
|
||||
Content="verschlüsseln"
|
||||
Margin="5 5 5 5 "
|
||||
Click="btnCrypt_Click" />
|
||||
<Button x:Name="btnDeCrypt"
|
||||
Content="entschlüsseln"
|
||||
Margin="5 5 5 5 "
|
||||
Click="btnDeCrypt_Click" />
|
||||
|
||||
</StackPanel>
|
||||
|
||||
<StatusBar Grid.Row="3">
|
||||
<StatusBarItem HorizontalAlignment="Right">
|
||||
<TextBlock Text="{Binding CurrentTime}" />
|
||||
</StatusBarItem>
|
||||
</StatusBar>
|
||||
|
||||
</Grid>
|
||||
</Window>
|
||||
61
FSI.Lib/Guis/DeEncryptMessage/FrmMain.xaml.cs
Normal file
61
FSI.Lib/Guis/DeEncryptMessage/FrmMain.xaml.cs
Normal file
@@ -0,0 +1,61 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Data;
|
||||
using System.Windows.Documents;
|
||||
using System.Windows.Input;
|
||||
using System.Windows.Media;
|
||||
using System.Windows.Media.Imaging;
|
||||
using System.Windows.Shapes;
|
||||
|
||||
namespace FSI.Lib.Guis.DeEncryptMessage
|
||||
{
|
||||
/// <summary>
|
||||
/// Interaktionslogik für FrmMain.xaml
|
||||
/// </summary>
|
||||
public partial class FrmMain : Window
|
||||
{
|
||||
public string Password { get; set; }
|
||||
public bool CloseAtLostFocus { get; set; }
|
||||
public FrmMain()
|
||||
{
|
||||
InitializeComponent();
|
||||
Deactivated += FrmMain_Deactivated;
|
||||
DataContext = new MVVM.ViewModel.CurrentTimeViewModel();
|
||||
}
|
||||
|
||||
private void FrmMain_Deactivated(object sender, System.EventArgs e)
|
||||
{
|
||||
if (CloseAtLostFocus)
|
||||
Visibility = Visibility.Hidden;
|
||||
}
|
||||
|
||||
private void btnDeCrypt_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
try
|
||||
{
|
||||
tboutput.Text = DeEncryptString.DeEncrypt.DecryptString(tbInput.Text, Password);
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
MessageBox.Show("Text kann nicht entschlüsselt werden!", "Achtung", MessageBoxButton.OK, MessageBoxImage.Error);
|
||||
}
|
||||
}
|
||||
|
||||
private void btnCrypt_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
try
|
||||
{
|
||||
tboutput.Text = DeEncryptString.DeEncrypt.CryptString(tbInput.Text, Password);
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
MessageBox.Show("Text kann nicht verschlüsselt werden!", "Achtung", MessageBoxButton.OK, MessageBoxImage.Error);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
116
FSI.Lib/Guis/Pdf.Mgt/Cmds.cs
Normal file
116
FSI.Lib/Guis/Pdf.Mgt/Cmds.cs
Normal file
@@ -0,0 +1,116 @@
|
||||
using System;
|
||||
using System.Configuration;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace FSI.Lib.Guis.Pdf.Mgt
|
||||
{
|
||||
public class Cmds
|
||||
{
|
||||
public static Task DelDirectoriesAsync(string path)
|
||||
{
|
||||
return Task.Factory.StartNew(() =>
|
||||
{
|
||||
Action<string> DelPath = null;
|
||||
DelPath = p =>
|
||||
{
|
||||
Directory.EnumerateFiles(p).ToList().ForEach(System.IO.File.Delete); // Dateien im Verzeichnis löschen
|
||||
Directory.EnumerateDirectories(p).ToList().ForEach(DelPath);
|
||||
Directory.EnumerateDirectories(p).ToList().ForEach(Directory.Delete); // Verzeichnis löschen
|
||||
};
|
||||
|
||||
if (Directory.Exists(path))
|
||||
{
|
||||
string[] directories = Directory.GetDirectories(path); // Alle Unterverzeichniss auslesen
|
||||
|
||||
foreach (string directory in directories)
|
||||
{
|
||||
if (!(directory.Replace(path, "")).StartsWith(".")) // Überprüfen ob Verzeichnis mit "." startet
|
||||
{
|
||||
DelPath(directory); // Unterverzeichnisse inkl. Dateien löschen
|
||||
Directory.Delete(directory); // Verzeichnis löschen
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public static Task DelFilesAsync(string path)
|
||||
{
|
||||
return Task.Factory.StartNew(() =>
|
||||
{
|
||||
var files = Directory.GetFiles(path);
|
||||
|
||||
foreach (string file in files)
|
||||
{
|
||||
FileInfo fileInfo = new FileInfo(file);
|
||||
|
||||
const int STR_LENGTH = 27; // min. Dateinamen Länge (Zahlen inkl. Bindestriche)
|
||||
|
||||
if (!fileInfo.Name.StartsWith(".")) // Überprüfen ob Datei mit "." startet
|
||||
{
|
||||
if (fileInfo.Extension != ".pdf" || fileInfo.Name.Length <= STR_LENGTH) // Überprüfen ob es sich um eine PDF-Datei handelt
|
||||
{
|
||||
System.IO.File.Delete(fileInfo.FullName); // ... wenn nicht Datei löschen
|
||||
}
|
||||
else
|
||||
{
|
||||
string[] fileStrings = fileInfo.Name.Substring(0, STR_LENGTH - 1).Split('-'); // Zahlenblock splitten für Überprüfung
|
||||
|
||||
if (fileStrings.Length == 3) // 3 Zahlenblöcke vorhanden?
|
||||
{
|
||||
if (fileStrings[0].Length != 10 || !Int64.TryParse(fileStrings[0], out _) || fileStrings[1].Length != 10 || !Int64.TryParse(fileStrings[1], out _) || fileStrings[2].Length != 4 || !Int64.TryParse(fileStrings[2], out _)) // Länge der Zahlenblöcke überprüfen
|
||||
{
|
||||
System.IO.File.Delete(fileInfo.FullName); // ..., wenn Länge nicht passt, Datei löschen
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
System.IO.File.Delete(fileInfo.FullName); // ..., wenn nicht Datei löschen
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public static Task CreatePdfShortcutsAsync(string path)
|
||||
{
|
||||
return Task.Factory.StartNew(() =>
|
||||
{
|
||||
var files = Directory.GetFiles(path, "*.pdf"); // alle PDF-Dateien einlesen
|
||||
|
||||
foreach (var file in files)
|
||||
{
|
||||
FileInfo fileInfo = new FileInfo(file);
|
||||
|
||||
string[] fileStrings = System.IO.Path.GetFileNameWithoutExtension(fileInfo.Name).Split(' '); // Datei-Namen für Verzeichnis Bezeichnung aufteilen
|
||||
|
||||
string tmpPath = path + fileStrings[1] + " " + fileStrings[2]; // Verzeichnis Bezeichnung zusammenstellen
|
||||
|
||||
if (Convert.ToBoolean(ConfigurationManager.AppSettings["SubDir"])) // mit/ohne Unterverzeichnis
|
||||
{
|
||||
tmpPath = path + fileStrings[1] + @"\" + fileStrings[2]; // mit Unterverzeichnis
|
||||
}
|
||||
else
|
||||
{
|
||||
tmpPath = path + fileStrings[1] + " " + fileStrings[2]; // ohne Unterverzeichnis
|
||||
}
|
||||
|
||||
Directory.CreateDirectory(tmpPath); // Verzeichnis erstellen
|
||||
|
||||
// Shortcut erstellen
|
||||
if (Convert.ToBoolean(ConfigurationManager.AppSettings["WithNo"]))
|
||||
{
|
||||
LnkParser.ShortCut.Create(System.IO.Path.GetFileNameWithoutExtension(tmpPath + "\\" + fileInfo.Name), tmpPath, fileInfo.FullName, System.IO.Path.GetFileNameWithoutExtension(fileInfo.FullName));
|
||||
}
|
||||
else
|
||||
{
|
||||
LnkParser.ShortCut.Create(System.IO.Path.GetFileNameWithoutExtension(fileInfo.Name.Replace(fileStrings[0], "")), tmpPath, fileInfo.FullName, System.IO.Path.GetFileNameWithoutExtension(fileInfo.FullName));
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
68
FSI.Lib/Guis/Pdf.Mgt/FrmMain.xaml
Normal file
68
FSI.Lib/Guis/Pdf.Mgt/FrmMain.xaml
Normal file
@@ -0,0 +1,68 @@
|
||||
<Window x:Class="FSI.Lib.Guis.Pdf.Mgt.FrmMain"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:local="clr-namespace:FSI.Lib.Guis.Pdf.Mgt"
|
||||
mc:Ignorable="d"
|
||||
Title="FSI Eplan PDF Mgt"
|
||||
SizeToContent="WidthAndHeight"
|
||||
Height="Auto"
|
||||
Width="Auto"
|
||||
Icon="../../Icons/FondiumU.ico"
|
||||
WindowStyle="ToolWindow">
|
||||
<Grid>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition MaxHeight="30"
|
||||
Height="*" />
|
||||
<RowDefinition />
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<Button x:Name="btnCleanUp"
|
||||
Content="aufräumen"
|
||||
Margin="5 5 5 5 "
|
||||
Click="btnCleanUp_Click" />
|
||||
<Button x:Name="btnCreateShortcuts"
|
||||
Content="Verknüpfungen erstellen"
|
||||
Margin="5 5 5 5 "
|
||||
Click="btnCreateShortcuts_Click" />
|
||||
<Button x:Name="btnStart"
|
||||
Content="alles ausführen"
|
||||
Margin="5 5 5 5 "
|
||||
Click="btnStart_Click" />
|
||||
</StackPanel>
|
||||
<StatusBar Grid.Row="2">
|
||||
<StatusBar.ItemsPanel>
|
||||
<ItemsPanelTemplate>
|
||||
<Grid>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="*" />
|
||||
</Grid.RowDefinitions>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="4*" />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
|
||||
</Grid.ColumnDefinitions>
|
||||
</Grid>
|
||||
</ItemsPanelTemplate>
|
||||
</StatusBar.ItemsPanel>
|
||||
<StatusBarItem>
|
||||
<TextBlock x:Name="tbStatus" />
|
||||
</StatusBarItem>
|
||||
<StatusBarItem Grid.Column="1">
|
||||
<ProgressBar x:Name="pgProgress"
|
||||
Width="80"
|
||||
Height="18" />
|
||||
</StatusBarItem>
|
||||
|
||||
|
||||
<StatusBarItem HorizontalAlignment="Right">
|
||||
<TextBlock Text="{Binding CurrentTime}" />
|
||||
</StatusBarItem>
|
||||
|
||||
|
||||
</StatusBar>
|
||||
</Grid>
|
||||
</Window>
|
||||
71
FSI.Lib/Guis/Pdf.Mgt/FrmMain.xaml.cs
Normal file
71
FSI.Lib/Guis/Pdf.Mgt/FrmMain.xaml.cs
Normal file
@@ -0,0 +1,71 @@
|
||||
using System.Configuration;
|
||||
using System.Reflection;
|
||||
using System.Windows;
|
||||
|
||||
namespace FSI.Lib.Guis.Pdf.Mgt
|
||||
{
|
||||
/// <summary>
|
||||
/// Interaktionslogik für FrmMain.xaml
|
||||
/// </summary>
|
||||
public partial class FrmMain : Window
|
||||
{
|
||||
public bool CloseAtLostFocus { get; set; }
|
||||
|
||||
public FrmMain()
|
||||
{
|
||||
InitializeComponent();
|
||||
DataContext = new MVVM.ViewModel.CurrentTimeViewModel();
|
||||
Title += " v" + Assembly.GetExecutingAssembly().GetName().Version; // Version in Titel eintragen
|
||||
Deactivated += FrmMain_Deactivated;
|
||||
}
|
||||
|
||||
private void FrmMain_Deactivated(object sender, System.EventArgs e)
|
||||
{
|
||||
if (CloseAtLostFocus)
|
||||
Visibility = Visibility.Hidden;
|
||||
}
|
||||
|
||||
private async void btnCleanUp_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
BtnMgt(false); // Schaltflächen sperren
|
||||
|
||||
await Cmds.DelDirectoriesAsync(ConfigurationManager.AppSettings["PdfPath"]); // nicht benötigte/zulässige Verzeichnisse löschen
|
||||
await Cmds.DelFilesAsync(ConfigurationManager.AppSettings["PdfPath"]); // nicht benötigte/zulässige Datien löschen
|
||||
BtnMgt(true); // Schaltflächen freigeben
|
||||
}
|
||||
|
||||
private async void btnCreateShortcuts_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
BtnMgt(false); // Schaltflächen sperren
|
||||
await Cmds.CreatePdfShortcutsAsync(ConfigurationManager.AppSettings["PdfPath"]); // Verzeichnisstruktur und Verknüpfungen erstellen
|
||||
BtnMgt(true); // Schaltflächen freigeben
|
||||
}
|
||||
|
||||
private async void btnStart_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
BtnMgt(false); // Schaltflächen sperren
|
||||
await Cmds.DelDirectoriesAsync(ConfigurationManager.AppSettings["PdfPath"]); // nicht benötigte/zulässige Verzeichnisse löschen
|
||||
await Cmds.DelFilesAsync(ConfigurationManager.AppSettings["PdfPath"]); // nicht benötigte/zulässige Datien lösche
|
||||
await Cmds.CreatePdfShortcutsAsync(ConfigurationManager.AppSettings["PdfPath"]); // Verzeichnisstruktur und Verknüpfungen erstellen
|
||||
BtnMgt(true); // Schaltflächen freigeben
|
||||
}
|
||||
|
||||
private void BtnMgt(bool enable)
|
||||
{
|
||||
btnCleanUp.IsEnabled =
|
||||
btnCreateShortcuts.IsEnabled =
|
||||
btnStart.IsEnabled = enable;
|
||||
pgProgress.IsIndeterminate = !enable;
|
||||
|
||||
// Status anzeige
|
||||
if (enable)
|
||||
{
|
||||
tbStatus.Text = "beendet";
|
||||
}
|
||||
else
|
||||
{
|
||||
tbStatus.Text = "gestartet";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
185
FSI.Lib/Guis/Prj.Mgt/FrmMain.xaml
Normal file
185
FSI.Lib/Guis/Prj.Mgt/FrmMain.xaml
Normal file
@@ -0,0 +1,185 @@
|
||||
<Window x:Class="FSI.Lib.Guis.Prj.Mgt.FrmMain"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:diagnostics="clr-namespace:System.Diagnostics;assembly=WindowsBase"
|
||||
xmlns:local="clr-namespace:FSI.Lib.Guis.Prj.Mgt"
|
||||
xmlns:control="clr-namespace:FSI.Lib.Wpf.Ctrls.FilterDataGrid"
|
||||
xmlns:viewmodel="clr-namespace:FSI.Lib.Guis.Prj.Mgt.ViewModel"
|
||||
d:DataContext="{d:DesignInstance Type=viewmodel:ViewModelPrj}"
|
||||
mc:Ignorable="d"
|
||||
SizeToContent="Width"
|
||||
Height="800"
|
||||
Width="Auto"
|
||||
Icon="../../Icons/FondiumU.ico">
|
||||
|
||||
<Window.Resources>
|
||||
<ObjectDataProvider x:Key="PrjsFiltered"></ObjectDataProvider>
|
||||
</Window.Resources>
|
||||
|
||||
<Window.InputBindings>
|
||||
<KeyBinding Command="{Binding CmdOpen}"
|
||||
Gesture="CTRL+o" />
|
||||
</Window.InputBindings>
|
||||
|
||||
<Grid FocusManager.FocusedElement="{Binding ElementName=tbSearch}">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="*" />
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<Grid Grid.Row="0"
|
||||
Margin="0,10"
|
||||
HorizontalAlignment="Stretch">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="2*" />
|
||||
<ColumnDefinition Width="1*" />
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<StackPanel Grid.Column="0"
|
||||
HorizontalAlignment="Left"
|
||||
Orientation="Horizontal">
|
||||
<Label Margin="0,0,20,0"
|
||||
VerticalAlignment="Bottom"
|
||||
Content="Suche:"
|
||||
FontWeight="Bold" />
|
||||
<TextBox Name="tbSearch"
|
||||
Height="26"
|
||||
MinWidth="200"
|
||||
VerticalAlignment="Center"
|
||||
VerticalContentAlignment="Center"
|
||||
Text="{Binding Search, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
|
||||
TextChanged="tbSearch_TextChanged" />
|
||||
<Button Width="Auto"
|
||||
Margin="5,0,0,0"
|
||||
Padding="4"
|
||||
Command="{Binding RefreshCommand}"
|
||||
ToolTip="Filter löschen"
|
||||
Cursor="Hand">
|
||||
<Image Source="../../Icons/Cross.png"
|
||||
Height="14" />
|
||||
</Button>
|
||||
</StackPanel>
|
||||
<StackPanel Grid.Column="2"
|
||||
HorizontalAlignment="Right"
|
||||
Orientation="Horizontal">
|
||||
<Button Width="Auto"
|
||||
Margin="0 0 20 0"
|
||||
Padding="4"
|
||||
ToolTip="öffen"
|
||||
Command="{Binding CmdOpen}"
|
||||
IsEnabled="{Binding CanExecuteOpen}"
|
||||
Cursor="Hand">
|
||||
<Image Source="../../Icons/Open.png"
|
||||
Height="14" />
|
||||
</Button>
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
|
||||
<StackPanel Grid.Row="1"
|
||||
HorizontalAlignment="Left"
|
||||
Orientation="Horizontal"
|
||||
Margin="0,5">
|
||||
<Label Margin="0,0,20,0"
|
||||
VerticalAlignment="Bottom"
|
||||
Content="Quick-Filter:"
|
||||
FontWeight="Bold" />
|
||||
<Button Width="Auto"
|
||||
Margin="5,0,0,0"
|
||||
Padding="4"
|
||||
ToolTip="Filter löschen"
|
||||
Command="{Binding RefreshCommand}"
|
||||
Cursor="Hand">
|
||||
<Image Source="../../Icons/Cross.png"
|
||||
Height="14" />
|
||||
</Button>
|
||||
<Button Content="PL1"
|
||||
Width="Auto"
|
||||
Margin="10,0,0,0"
|
||||
ToolTip="Filter auf PL1"
|
||||
Command="{Binding CmdQuickSearch}"
|
||||
CommandParameter="PL1"
|
||||
FocusManager.FocusedElement="{Binding ElementName=tbSearch}" />
|
||||
<Button Content="PL2"
|
||||
Width="Auto"
|
||||
Margin="10,0,0,0"
|
||||
ToolTip="Filter auf PL2"
|
||||
Command="{Binding CmdQuickSearch}"
|
||||
CommandParameter="PL2"
|
||||
FocusManager.FocusedElement="{Binding ElementName=tbSearch}" />
|
||||
<Button Content="PL3"
|
||||
Width="Auto"
|
||||
Margin="10,0,0,0"
|
||||
ToolTip="Filter auf PL3"
|
||||
Command="{Binding CmdQuickSearch}"
|
||||
CommandParameter="PL3"
|
||||
FocusManager.FocusedElement="{Binding ElementName=tbSearch}" />
|
||||
<Button Content="SMZ"
|
||||
Width="Auto"
|
||||
Margin="10,0,0,0"
|
||||
ToolTip="Filter auf SMZ"
|
||||
Command="{Binding CmdQuickSearch}"
|
||||
CommandParameter="SMZ"
|
||||
FocusManager.FocusedElement="{Binding ElementName=tbSearch}" />
|
||||
</StackPanel>
|
||||
|
||||
<control:FilterDataGrid x:Name="FilterDataGrid"
|
||||
Grid.Row="2"
|
||||
AutoGenerateColumns="False"
|
||||
DateFormatString="d"
|
||||
FilterLanguage="German"
|
||||
ItemsSource="{Binding PrjsFiltered, UpdateSourceTrigger=PropertyChanged}"
|
||||
SelectionMode="Single"
|
||||
SelectedItem="{Binding SeletctedPrj, UpdateSourceTrigger=PropertyChanged}"
|
||||
ShowElapsedTime="false"
|
||||
ShowRowsCount="True"
|
||||
ShowStatusBar="True">
|
||||
|
||||
<control:FilterDataGrid.InputBindings>
|
||||
<MouseBinding MouseAction="LeftDoubleClick"
|
||||
Command="{Binding CmdOpen}" />
|
||||
|
||||
</control:FilterDataGrid.InputBindings>
|
||||
<control:FilterDataGrid.Columns>
|
||||
|
||||
<control:DataGridTemplateColumn FieldName="PlantNo"
|
||||
Header="Anlagen-Nr."
|
||||
IsColumnFiltered="True"
|
||||
SortMemberPath="PlantNo">
|
||||
<control:DataGridTemplateColumn.CellTemplate>
|
||||
<DataTemplate DataType="local:Prj">
|
||||
<TextBlock Text="{Binding PlantNo}" />
|
||||
</DataTemplate>
|
||||
</control:DataGridTemplateColumn.CellTemplate>
|
||||
</control:DataGridTemplateColumn>
|
||||
|
||||
<control:DataGridTextColumn Binding="{Binding SubPlantNo}"
|
||||
Header="Teilanlagen-Nr."
|
||||
IsReadOnly="True"
|
||||
IsColumnFiltered="True" />
|
||||
|
||||
<control:DataGridTextColumn Binding="{Binding No, StringFormat={}{0:0000}}"
|
||||
Header="Lfd.-Nr."
|
||||
IsReadOnly="True"
|
||||
IsColumnFiltered="True" />
|
||||
|
||||
<control:DataGridTextColumn Binding="{Binding Plant}"
|
||||
Header="Anlage"
|
||||
IsReadOnly="True"
|
||||
IsColumnFiltered="True" />
|
||||
|
||||
<control:DataGridTextColumn Binding="{Binding SubPlant}"
|
||||
Header="Teilanlage"
|
||||
IsReadOnly="True"
|
||||
IsColumnFiltered="True" />
|
||||
|
||||
<control:DataGridTextColumn Binding="{Binding Description}"
|
||||
Header="Bezeichnung"
|
||||
IsReadOnly="True"
|
||||
IsColumnFiltered="True" />
|
||||
</control:FilterDataGrid.Columns>
|
||||
</control:FilterDataGrid>
|
||||
</Grid>
|
||||
</Window>
|
||||
64
FSI.Lib/Guis/Prj.Mgt/FrmMain.xaml.cs
Normal file
64
FSI.Lib/Guis/Prj.Mgt/FrmMain.xaml.cs
Normal file
@@ -0,0 +1,64 @@
|
||||
using FSI.Lib.Guis.Prj.Mgt.ViewModel;
|
||||
using System.Reflection;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
|
||||
namespace FSI.Lib.Guis.Prj.Mgt
|
||||
{
|
||||
/// <summary>
|
||||
/// Interaktionslogik für Main.xaml
|
||||
/// </summary>
|
||||
public partial class FrmMain : Window
|
||||
{
|
||||
public ViewModelPrj Prj { get; set; }
|
||||
public bool ShowPdf { get; set; }
|
||||
public bool CloseAtLostFocus { get; set; }
|
||||
public string Path { get; set; }
|
||||
public string[] EplExes { get; set; }
|
||||
|
||||
public FrmMain()
|
||||
{
|
||||
InitializeComponent();
|
||||
|
||||
Loaded += Main_Loaded;
|
||||
Deactivated += FrmMain_Deactivated;
|
||||
}
|
||||
|
||||
private void FrmMain_Deactivated(object sender, System.EventArgs e)
|
||||
{
|
||||
if (CloseAtLostFocus)
|
||||
Visibility = Visibility.Hidden;
|
||||
}
|
||||
|
||||
private void Main_Loaded(object sender, RoutedEventArgs e)
|
||||
{
|
||||
|
||||
|
||||
if (ShowPdf)
|
||||
{
|
||||
Title = "FSI PDF-Auswahl";
|
||||
}
|
||||
else
|
||||
{
|
||||
Title = "FSI Epl Projektauswahl";
|
||||
}
|
||||
|
||||
Title += " v" + Assembly.GetExecutingAssembly().GetName().Version; // Version in Titel eintragen
|
||||
|
||||
Prj = new ViewModelPrj(new PrjDataProvider())
|
||||
{
|
||||
DataPath = Path,
|
||||
ShowPdf = ShowPdf,
|
||||
EplExes = EplExes,
|
||||
};
|
||||
DataContext = Prj;
|
||||
|
||||
Prj.Load();
|
||||
}
|
||||
|
||||
private void tbSearch_TextChanged(object sender, TextChangedEventArgs e)
|
||||
{
|
||||
tbSearch.Select(tbSearch.Text.Length, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
25
FSI.Lib/Guis/Prj.Mgt/Model/Prj.cs
Normal file
25
FSI.Lib/Guis/Prj.Mgt/Model/Prj.cs
Normal file
@@ -0,0 +1,25 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace FSI.Lib.Guis.Prj.Mgt.Model
|
||||
{
|
||||
public class Prj
|
||||
{
|
||||
public Int64 PlantNo { get; set; }
|
||||
public Int64 SubPlantNo { get; set; }
|
||||
public Int64 No { get; set; }
|
||||
public string Plant { get; set; }
|
||||
public string SubPlant { get; set; }
|
||||
public string Description { get; set; }
|
||||
public string DescriptionDetail { get; set; }
|
||||
public string FullName { get; set; }
|
||||
}
|
||||
|
||||
public interface IPrjDataProvider
|
||||
{
|
||||
IEnumerable<Prj> Load(string path, bool showPdf);
|
||||
}
|
||||
}
|
||||
208
FSI.Lib/Guis/Prj.Mgt/ViewModel/ViewModelPrj.cs
Normal file
208
FSI.Lib/Guis/Prj.Mgt/ViewModel/ViewModelPrj.cs
Normal file
@@ -0,0 +1,208 @@
|
||||
using FSI.Lib.Guis.Prj.Mgt.Model;
|
||||
using FSI.Lib.MVVM;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.ComponentModel;
|
||||
using System.Configuration;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Windows.Data;
|
||||
using System.Windows.Input;
|
||||
|
||||
namespace FSI.Lib.Guis.Prj.Mgt.ViewModel
|
||||
{
|
||||
public class ViewModelPrj : MVVM.ViewModelBase
|
||||
{
|
||||
readonly IPrjDataProvider _prjDataProvider;
|
||||
private string _search;
|
||||
private ICollectionView _collView;
|
||||
|
||||
public ICommand RefreshCommand => new DelegateCommand(RefreshData);
|
||||
private ICommand _cmdQuickSearch;
|
||||
private ICommand _cmdOpen;
|
||||
|
||||
public ViewModelPrj(IPrjDataProvider prjDataProvider)
|
||||
{
|
||||
Prjs = new ObservableCollection<Model.Prj>();
|
||||
_prjDataProvider = prjDataProvider;
|
||||
|
||||
_cmdQuickSearch = new RelayCommand<object>(ExecuteQuickSearch, CanExecuteQuickSearch);
|
||||
_cmdOpen = new RelayCommand<object>(ExecuteOpen, CanExecuteOpen);
|
||||
}
|
||||
|
||||
public ObservableCollection<Model.Prj> Prjs { get; }
|
||||
public ObservableCollection<Model.Prj> PrjsFiltered { get; set; }
|
||||
|
||||
public Model.Prj SeletctedPrj { get; set; }
|
||||
|
||||
public string DataPath { get; set; }
|
||||
|
||||
public bool ShowPdf { get; set; }
|
||||
public string[] EplExes { get; set; }
|
||||
|
||||
public void Load()
|
||||
{
|
||||
var prjs = _prjDataProvider.Load(DataPath, ShowPdf);
|
||||
Prjs.Clear();
|
||||
|
||||
if (prjs != null)
|
||||
{
|
||||
foreach (Model.Prj prj in prjs)
|
||||
{
|
||||
Prjs.Add(prj);
|
||||
}
|
||||
}
|
||||
|
||||
PrjsFiltered = new ObservableCollection<Model.Prj>(Prjs);
|
||||
_collView = CollectionViewSource.GetDefaultView(PrjsFiltered);
|
||||
}
|
||||
|
||||
public string Search
|
||||
{
|
||||
get => _search;
|
||||
set
|
||||
{
|
||||
_search = value;
|
||||
|
||||
_collView.Filter = e =>
|
||||
{
|
||||
var item = (Model.Prj)e;
|
||||
return item != null && ((item.Plant?.StartsWith(_search, StringComparison.OrdinalIgnoreCase) ?? false)
|
||||
|| (item.SubPlant?.StartsWith(_search, StringComparison.OrdinalIgnoreCase) ?? false)
|
||||
#if NET472
|
||||
|| (item.Description?.Contains(_search) ?? false)
|
||||
|| (item.DescriptionDetail?.Contains(_search) ?? false)
|
||||
#elif NET6_0
|
||||
|| (item.Description?.Contains(_search, StringComparison.OrdinalIgnoreCase) ?? false)
|
||||
|| (item.DescriptionDetail?.Contains(_search, StringComparison.OrdinalIgnoreCase) ?? false)
|
||||
#endif
|
||||
);
|
||||
};
|
||||
|
||||
_collView.Refresh();
|
||||
PrjsFiltered = new ObservableCollection<Model.Prj>(_collView.OfType<Model.Prj>().ToList());
|
||||
OnPropertyChanged();
|
||||
}
|
||||
}
|
||||
|
||||
public void QuickSearch(string search)
|
||||
{
|
||||
Search = search + " ";
|
||||
}
|
||||
|
||||
private void RefreshData(object obj)
|
||||
{
|
||||
Search = string.Empty;
|
||||
}
|
||||
|
||||
private bool CanExecuteQuickSearch(object obj)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
private void ExecuteQuickSearch(object obj)
|
||||
{
|
||||
QuickSearch(obj.ToString());
|
||||
}
|
||||
|
||||
public ICommand CmdQuickSearch
|
||||
{
|
||||
get { return _cmdQuickSearch; }
|
||||
set => _cmdQuickSearch = value;
|
||||
}
|
||||
|
||||
private bool CanExecuteOpen(object obj)
|
||||
{
|
||||
if (SeletctedPrj != null)
|
||||
return true;
|
||||
else
|
||||
return false;
|
||||
}
|
||||
|
||||
private void ExecuteOpen(object obj)
|
||||
{
|
||||
if (ShowPdf)
|
||||
{
|
||||
new Process
|
||||
{
|
||||
StartInfo = new ProcessStartInfo(SeletctedPrj.FullName)
|
||||
{
|
||||
UseShellExecute = true
|
||||
}
|
||||
}.Start();
|
||||
}
|
||||
else
|
||||
{
|
||||
var arguments = " /Variant:\"Electric P8\" ProjectOpen /Project:\"" + SeletctedPrj.FullName + "\"";
|
||||
|
||||
string fileName = string.Empty;
|
||||
string path = string.Empty;
|
||||
|
||||
for (int i = 0; i <= EplExes.Length - 1; i++)
|
||||
{
|
||||
if (File.Exists(EplExes[i].Trim()))
|
||||
{
|
||||
fileName = EplExes[i].Trim();
|
||||
}
|
||||
}
|
||||
|
||||
new Process
|
||||
{
|
||||
StartInfo = new ProcessStartInfo()
|
||||
{
|
||||
FileName = fileName,
|
||||
Arguments = arguments,
|
||||
}
|
||||
}.Start();
|
||||
}
|
||||
}
|
||||
|
||||
public ICommand CmdOpen
|
||||
{
|
||||
get { return _cmdOpen; }
|
||||
set => _cmdOpen = value;
|
||||
}
|
||||
}
|
||||
|
||||
public class PrjDataProvider : IPrjDataProvider
|
||||
{
|
||||
public IEnumerable<Model.Prj> Load(string path, bool showPdf)
|
||||
{
|
||||
var prjs = new ObservableCollection<Model.Prj>();
|
||||
string[] files = Directory.GetFiles(path); // alle PDF-Dateien einlesen
|
||||
|
||||
foreach (var file in files)
|
||||
{
|
||||
if (((file.EndsWith(".elk") || file.EndsWith(".elp") || file.EndsWith(".els") || file.EndsWith(".elx") || file.EndsWith(".elr") || file.EndsWith(".ell") || file.EndsWith(".elf"))
|
||||
&& !showPdf) || (file.EndsWith(".pdf") && showPdf))
|
||||
{
|
||||
Model.Prj prj = new Model.Prj();
|
||||
FileInfo fileInfo = new FileInfo(file);
|
||||
string[] nameNo = fileInfo.Name.Substring(0, 27 - 1).Split('-');
|
||||
|
||||
if (nameNo.Length == 3) // 3 Zahlenblöcke vorhanden?
|
||||
{
|
||||
if (nameNo[0].Length == 10 || Int64.TryParse(nameNo[0], out _) || nameNo[1].Length == 10 || Int64.TryParse(nameNo[1], out _) || nameNo[2].Length == 4 || !Int64.TryParse(nameNo[2], out _)) // Länge der Zahlenblöcke überprüfen
|
||||
{
|
||||
prj.PlantNo = Int64.Parse(nameNo[0]);
|
||||
prj.SubPlantNo = Int64.Parse(nameNo[1]);
|
||||
prj.No = Int64.Parse(nameNo[2]);
|
||||
|
||||
string[] fileStrings = System.IO.Path.GetFileNameWithoutExtension(fileInfo.Name).Split(' ');
|
||||
prj.Plant = fileStrings[1];
|
||||
prj.SubPlant = fileStrings[2];
|
||||
prj.Description = System.IO.Path.GetFileNameWithoutExtension(fileInfo.Name).Replace(nameNo[0] + "-" + nameNo[1] + "-" + nameNo[2] + " " + fileStrings[1] + " " + fileStrings[2], "");
|
||||
prj.Description = prj.Description.Trim();
|
||||
prj.DescriptionDetail = prj.Plant + " " + prj.SubPlant + " " + prj.Description;
|
||||
prj.FullName = fileInfo.FullName;
|
||||
prjs.Add(prj);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return prjs;
|
||||
}
|
||||
}
|
||||
}
|
||||
Binary file not shown.
54
FSI.Lib/Guis/SieStarterCsvExporter/FrmMain.xaml
Normal file
54
FSI.Lib/Guis/SieStarterCsvExporter/FrmMain.xaml
Normal file
@@ -0,0 +1,54 @@
|
||||
<Window x:Class="FSI.Lib.Guis.SieStarterCsvExporter.FrmMain"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
mc:Ignorable="d"
|
||||
Title="FSI Siemens Starter Trace Csv-Exporter"
|
||||
SizeToContent="WidthAndHeight"
|
||||
Height="Auto"
|
||||
Width="Auto"
|
||||
Icon="../../Icons/FondiumU.ico"
|
||||
WindowStyle="ToolWindow">
|
||||
<Grid>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<TextBlock Text="Datei:"
|
||||
Margin="5 5 5 5" />
|
||||
<TextBox x:Name="tbSrcFile"
|
||||
Width="600"
|
||||
Margin="5 5 5 5"
|
||||
AllowDrop="True"
|
||||
TextChanged="TbSrcFile_TextChanged"
|
||||
PreviewDragEnter="TbSrcFile_PreviewDragEnter"
|
||||
PreviewDragOver="TbSrcFile_PreviewDragOver"
|
||||
Drop="TbSrcFile_Drop" />
|
||||
|
||||
<Button x:Name="btnSlctSrcFile"
|
||||
Content="..."
|
||||
Width="30"
|
||||
Margin="5 5 5 5"
|
||||
Click="BtnSlctSrcFile_Click" />
|
||||
</StackPanel>
|
||||
|
||||
<StackPanel Grid.Row="1">
|
||||
<Button x:Name="btnStart"
|
||||
Content="Start"
|
||||
Margin="5 5 5 5"
|
||||
IsEnabled="False"
|
||||
Click="BtnStart_Click" />
|
||||
</StackPanel>
|
||||
|
||||
<StatusBar Grid.Row="2">
|
||||
<StatusBarItem HorizontalAlignment="Right">
|
||||
<TextBlock Text="{Binding CurrentTime}" />
|
||||
</StatusBarItem>
|
||||
</StatusBar>
|
||||
|
||||
</Grid>
|
||||
</Window>
|
||||
111
FSI.Lib/Guis/SieStarterCsvExporter/FrmMain.xaml.cs
Normal file
111
FSI.Lib/Guis/SieStarterCsvExporter/FrmMain.xaml.cs
Normal file
@@ -0,0 +1,111 @@
|
||||
using Microsoft.Win32;
|
||||
using System.Collections.Specialized;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Reflection;
|
||||
using System.Text;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
|
||||
namespace FSI.Lib.Guis.SieStarterCsvExporter
|
||||
{
|
||||
/// <summary>
|
||||
/// Interaktionslogik für frmMain.xaml
|
||||
/// </summary>
|
||||
public partial class FrmMain : Window
|
||||
{
|
||||
public FrmMain()
|
||||
{
|
||||
InitializeComponent();
|
||||
DataContext = new MVVM.ViewModel.CurrentTimeViewModel();
|
||||
}
|
||||
private void BtnSlctSrcFile_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
OpenFileDialog dlg = new OpenFileDialog()
|
||||
{
|
||||
Filter = "Trace (*.trc)|*.trc|alle Dateien (*:*)|*.*"
|
||||
};
|
||||
|
||||
if ((bool)dlg.ShowDialog())
|
||||
{
|
||||
tbSrcFile.Text = dlg.FileName;
|
||||
}
|
||||
}
|
||||
|
||||
private void BtnStart_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
string[] tbEntries = tbSrcFile.Text.Split('\n');
|
||||
|
||||
foreach (string tbEntry in tbEntries)
|
||||
{
|
||||
if (!string.IsNullOrEmpty(tbEntry))
|
||||
{
|
||||
Process process= new Process();
|
||||
process.StartInfo.FileName = Directory.GetCurrentDirectory() + @"\Guis\SieStarterCsvExporter\" + "Convert_SINAMICS_trace_CSV.exe";
|
||||
process.StartInfo.Arguments = "\"" + tbEntry + "\"";
|
||||
|
||||
process.Start();
|
||||
process.WaitForExit();
|
||||
}
|
||||
}
|
||||
|
||||
tbSrcFile.Text = string.Empty;
|
||||
}
|
||||
|
||||
private void TbSrcFile_PreviewDragEnter(object sender, System.Windows.DragEventArgs e)
|
||||
{
|
||||
if (e.Data.GetDataPresent(System.Windows.DataFormats.FileDrop))
|
||||
{
|
||||
e.Effects = System.Windows.DragDropEffects.Copy;
|
||||
}
|
||||
else
|
||||
{
|
||||
e.Effects = System.Windows.DragDropEffects.None;
|
||||
}
|
||||
}
|
||||
|
||||
private void TbSrcFile_PreviewDragOver(object sender, System.Windows.DragEventArgs e)
|
||||
{
|
||||
e.Handled = true;
|
||||
}
|
||||
|
||||
private void TbSrcFile_Drop(object sender, System.Windows.DragEventArgs e)
|
||||
{
|
||||
// Get data object
|
||||
var dataObject = e.Data as System.Windows.DataObject;
|
||||
|
||||
// Check for file list
|
||||
if (dataObject.ContainsFileDropList())
|
||||
{
|
||||
// Clear values
|
||||
tbSrcFile.Text = string.Empty;
|
||||
|
||||
// Process file names
|
||||
StringCollection fileNames = dataObject.GetFileDropList();
|
||||
StringBuilder bd = new StringBuilder();
|
||||
foreach (string fileName in fileNames)
|
||||
{
|
||||
if (fileName.EndsWith(".trc"))
|
||||
{
|
||||
bd.Append(fileName + "\n");
|
||||
}
|
||||
}
|
||||
|
||||
// Set text
|
||||
tbSrcFile.Text = bd.ToString();
|
||||
}
|
||||
}
|
||||
|
||||
private void TbSrcFile_TextChanged(object sender, TextChangedEventArgs e)
|
||||
{
|
||||
if (!string.IsNullOrEmpty(tbSrcFile.Text))
|
||||
{
|
||||
btnStart.IsEnabled = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
btnStart.IsEnabled = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
59
FSI.Lib/Guis/SieTiaWinCCMsgMgt/FrmMain.xaml
Normal file
59
FSI.Lib/Guis/SieTiaWinCCMsgMgt/FrmMain.xaml
Normal file
@@ -0,0 +1,59 @@
|
||||
<Window x:Class="FSI.Lib.Guis.SieTiaWinCCMsgMgt.FrmMain"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:local="clr-namespace:FSI.Lib.Guis.SieTiaWinCCMsgMgt"
|
||||
mc:Ignorable="d"
|
||||
Title="FSI WinCC Mgt"
|
||||
SizeToContent="WidthAndHeight"
|
||||
Height="Auto"
|
||||
Width="Auto"
|
||||
MinWidth="200"
|
||||
Icon="../../Icons/FondiumU.ico"
|
||||
WindowStyle="ToolWindow">
|
||||
<Grid>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition MaxHeight="30"
|
||||
Height="*" />
|
||||
<RowDefinition />
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<Button x:Name="btnStart"
|
||||
Content="Start"
|
||||
Margin="5 5 5 5 "
|
||||
Click="btnStart_Click" />
|
||||
<Button x:Name="btnStop"
|
||||
Content="Stop"
|
||||
Margin="5 5 5 5 "
|
||||
Click="btnStop_Click" />
|
||||
</StackPanel>
|
||||
|
||||
<StatusBar Grid.Row="2">
|
||||
<StatusBar.ItemsPanel>
|
||||
<ItemsPanelTemplate>
|
||||
<Grid>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="*" />
|
||||
</Grid.RowDefinitions>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="4*" />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
|
||||
</Grid.ColumnDefinitions>
|
||||
</Grid>
|
||||
</ItemsPanelTemplate>
|
||||
</StatusBar.ItemsPanel>
|
||||
<StatusBarItem>
|
||||
<TextBlock x:Name="tbStatus" />
|
||||
</StatusBarItem>
|
||||
<StatusBarItem Grid.Column="1">
|
||||
<ProgressBar x:Name="pgProgress"
|
||||
Width="80"
|
||||
Height="18" />
|
||||
</StatusBarItem>
|
||||
</StatusBar>
|
||||
</Grid>
|
||||
</Window>
|
||||
|
||||
57
FSI.Lib/Guis/SieTiaWinCCMsgMgt/FrmMain.xaml.cs
Normal file
57
FSI.Lib/Guis/SieTiaWinCCMsgMgt/FrmMain.xaml.cs
Normal file
@@ -0,0 +1,57 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Data;
|
||||
using System.Windows.Documents;
|
||||
using System.Windows.Input;
|
||||
using System.Windows.Media;
|
||||
using System.Windows.Media.Imaging;
|
||||
using System.Windows.Shapes;
|
||||
|
||||
namespace FSI.Lib.Guis.SieTiaWinCCMsgMgt
|
||||
{
|
||||
/// <summary>
|
||||
/// Interaktionslogik für FrmMain.xaml
|
||||
/// </summary>
|
||||
public partial class FrmMain : Window
|
||||
{
|
||||
public WinCC WinCC { get; set; }
|
||||
|
||||
public FrmMain()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
|
||||
private void btnStart_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
WinCC.Start();
|
||||
CtrlMgt();
|
||||
}
|
||||
|
||||
private void btnStop_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
WinCC.Stop();
|
||||
CtrlMgt();
|
||||
}
|
||||
|
||||
private void CtrlMgt()
|
||||
{
|
||||
if (WinCC.Status)
|
||||
{
|
||||
btnStart.IsEnabled = false;
|
||||
btnStop.IsEnabled = true;
|
||||
pgProgress.IsIndeterminate = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
btnStart.IsEnabled = true;
|
||||
btnStop.IsEnabled = false;
|
||||
pgProgress.IsIndeterminate = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
172
FSI.Lib/Guis/SieTiaWinCCMsgMgt/WinCC.cs
Normal file
172
FSI.Lib/Guis/SieTiaWinCCMsgMgt/WinCC.cs
Normal file
@@ -0,0 +1,172 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.Linq;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace FSI.Lib.Guis.SieTiaWinCCMsgMgt
|
||||
{
|
||||
public class WinCC
|
||||
{
|
||||
[DllImport("user32.dll")]
|
||||
public static extern IntPtr FindWindow(string lpClassName, string lpWindowName);
|
||||
|
||||
[DllImport("user32.dll")]
|
||||
public static extern bool SetForegroundWindow(IntPtr hWnd);
|
||||
|
||||
|
||||
[DllImport("user32.dll", CharSet = CharSet.Auto)]
|
||||
static extern IntPtr SendMessage(IntPtr hWnd, UInt32 Msg, IntPtr wParam, IntPtr lParam);
|
||||
|
||||
|
||||
[DllImport("user32.dll")]
|
||||
static extern IntPtr FindWindowEx(IntPtr hwndParent, IntPtr hwndChildAfter, string lpszClass, string lpszWindow);
|
||||
|
||||
[DllImport("user32.dll")]
|
||||
static extern IntPtr PostMessage(IntPtr hwndParent, int msg, int wParam, IntPtr lParam);
|
||||
|
||||
[DllImport("user32.dll")]
|
||||
[return: MarshalAs(UnmanagedType.Bool)]
|
||||
static extern bool IsWindowVisible(IntPtr hWnd);
|
||||
|
||||
const int BM_CLICK = 0x00F5; //message for Click which is a constant
|
||||
const int WM_LBUTTONDOWN = 0x0201; //message for Mouse down
|
||||
const int WM_LBUTTONUP = 0x0202; // message for Mouse up
|
||||
|
||||
private CancellationTokenSource tokenSource = null;
|
||||
private CancellationToken token;
|
||||
private Task task = null;
|
||||
|
||||
private BackgroundWorker backgroundWorker;
|
||||
|
||||
|
||||
public bool AutoStart { get; set; }
|
||||
public int UpdateIntervall { get; set; }
|
||||
public string WindowsName { get; set; }
|
||||
public string WindowsClassName { get; set; }
|
||||
public string ButtonName { get; set; }
|
||||
|
||||
public WinCC()
|
||||
{
|
||||
backgroundWorker = new BackgroundWorker
|
||||
{
|
||||
WorkerReportsProgress = true,
|
||||
WorkerSupportsCancellation = true
|
||||
};
|
||||
backgroundWorker.DoWork += BackgroundWorker_DoWork;
|
||||
backgroundWorker.ProgressChanged += BackgroundWorker_ProgressChanged;
|
||||
|
||||
if (AutoStart)
|
||||
{
|
||||
Start();
|
||||
}
|
||||
}
|
||||
|
||||
private void BackgroundWorker_ProgressChanged(object sender, ProgressChangedEventArgs e)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
private void BackgroundWorker_DoWork(object sender, DoWorkEventArgs e)
|
||||
{
|
||||
BackgroundWorker worker = sender as BackgroundWorker;
|
||||
|
||||
while (true)
|
||||
{
|
||||
if (worker.CancellationPending == true)
|
||||
{
|
||||
e.Cancel = true;
|
||||
break;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Perform a time consuming operation and report progress.
|
||||
System.Threading.Thread.Sleep(UpdateIntervall);
|
||||
|
||||
// Find windos by Name
|
||||
var windowHandle = FindWindow(WindowsClassName, WindowsName);
|
||||
|
||||
if (windowHandle != IntPtr.Zero && IsWindowVisible(windowHandle))
|
||||
{
|
||||
SetForegroundWindow(windowHandle);
|
||||
var btnHandle = FindWindowEx(windowHandle, IntPtr.Zero, null, ButtonName);
|
||||
SendMessage(btnHandle, BM_CLICK, IntPtr.Zero, IntPtr.Zero);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void Start()
|
||||
{
|
||||
if (backgroundWorker.IsBusy != true)
|
||||
{
|
||||
backgroundWorker.RunWorkerAsync();
|
||||
}
|
||||
Status = true;
|
||||
}
|
||||
|
||||
public void Stop()
|
||||
{
|
||||
if (backgroundWorker.WorkerSupportsCancellation == true)
|
||||
{
|
||||
backgroundWorker.CancelAsync();
|
||||
}
|
||||
Status = false;
|
||||
}
|
||||
|
||||
public bool Status { get; set; }
|
||||
|
||||
private void WinCCMsgMgt()
|
||||
{
|
||||
while (true)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (token.IsCancellationRequested)
|
||||
{
|
||||
token.ThrowIfCancellationRequested();
|
||||
}
|
||||
//MessageBox.Show("13456");
|
||||
Thread.Sleep(5000);
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected virtual void Dispose(bool disposing)
|
||||
{
|
||||
bool disposedValue = false;
|
||||
|
||||
try
|
||||
{
|
||||
if (!disposedValue)
|
||||
{
|
||||
if (disposing)
|
||||
{
|
||||
tokenSource.Cancel();
|
||||
task.Wait();
|
||||
tokenSource.Dispose();
|
||||
task.Dispose();
|
||||
}
|
||||
|
||||
disposedValue = true;
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
}
|
||||
finally
|
||||
{
|
||||
tokenSource = null;
|
||||
task = null;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
16
FSI.Lib/Helpers/IdentityHelpers.cs
Normal file
16
FSI.Lib/Helpers/IdentityHelpers.cs
Normal file
@@ -0,0 +1,16 @@
|
||||
using System.Security.Principal;
|
||||
|
||||
namespace FSI.Lib.Helpers
|
||||
{
|
||||
public static class IdentityHelpers
|
||||
{
|
||||
public static string ShortName(this WindowsIdentity Identity)
|
||||
{
|
||||
if (null != Identity)
|
||||
{
|
||||
return Identity.Name.Split(new char[] { '\\' })[1];
|
||||
}
|
||||
return string.Empty;
|
||||
}
|
||||
}
|
||||
}
|
||||
BIN
FSI.Lib/Icons/Cross.png
Normal file
BIN
FSI.Lib/Icons/Cross.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 23 KiB |
BIN
FSI.Lib/Icons/FondiumU.ico
Normal file
BIN
FSI.Lib/Icons/FondiumU.ico
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 5.4 KiB |
BIN
FSI.Lib/Icons/Open.png
Normal file
BIN
FSI.Lib/Icons/Open.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 11 KiB |
57
FSI.Lib/LnkParser/ShortCut.cs
Normal file
57
FSI.Lib/LnkParser/ShortCut.cs
Normal file
@@ -0,0 +1,57 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Runtime.InteropServices.ComTypes;
|
||||
using System.Text;
|
||||
|
||||
namespace FSI.Lib.LnkParser
|
||||
{
|
||||
public static class ShortCut
|
||||
{
|
||||
public static void Create(string shortcutName, string shortcutPath, string targetPath, string descripton)
|
||||
{
|
||||
IShellLink link = (IShellLink)new ShellLink();
|
||||
|
||||
// setup shortcut information
|
||||
link.SetPath(targetPath);
|
||||
link.SetDescription(descripton);
|
||||
|
||||
// save it
|
||||
IPersistFile file = (IPersistFile)link;
|
||||
file.Save(Path.Combine(shortcutPath, shortcutName) + ".lnk", false);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
[ComImport]
|
||||
[Guid("00021401-0000-0000-C000-000000000046")]
|
||||
internal class ShellLink
|
||||
{
|
||||
}
|
||||
|
||||
[ComImport]
|
||||
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
|
||||
[Guid("000214F9-0000-0000-C000-000000000046")]
|
||||
internal interface IShellLink
|
||||
{
|
||||
void GetPath([Out, MarshalAs(UnmanagedType.LPWStr)] StringBuilder pszFile, int cchMaxPath, out IntPtr pfd, int fFlags);
|
||||
void GetIDList(out IntPtr ppidl);
|
||||
void SetIDList(IntPtr pidl);
|
||||
void GetDescription([Out, MarshalAs(UnmanagedType.LPWStr)] StringBuilder pszName, int cchMaxName);
|
||||
void SetDescription([MarshalAs(UnmanagedType.LPWStr)] string pszName);
|
||||
void GetWorkingDirectory([Out, MarshalAs(UnmanagedType.LPWStr)] StringBuilder pszDir, int cchMaxPath);
|
||||
void SetWorkingDirectory([MarshalAs(UnmanagedType.LPWStr)] string pszDir);
|
||||
void GetArguments([Out, MarshalAs(UnmanagedType.LPWStr)] StringBuilder pszArgs, int cchMaxPath);
|
||||
void SetArguments([MarshalAs(UnmanagedType.LPWStr)] string pszArgs);
|
||||
void GetHotkey(out short pwHotkey);
|
||||
void SetHotkey(short wHotkey);
|
||||
void GetShowCmd(out int piShowCmd);
|
||||
void SetShowCmd(int iShowCmd);
|
||||
void GetIconLocation([Out, MarshalAs(UnmanagedType.LPWStr)] StringBuilder pszIconPath, int cchIconPath, out int piIcon);
|
||||
void SetIconLocation([MarshalAs(UnmanagedType.LPWStr)] string pszIconPath, int iIcon);
|
||||
void SetRelativePath([MarshalAs(UnmanagedType.LPWStr)] string pszPathRel, int dwReserved);
|
||||
void Resolve(IntPtr hwnd, int fFlags);
|
||||
void SetPath([MarshalAs(UnmanagedType.LPWStr)] string pszFile);
|
||||
}
|
||||
}
|
||||
|
||||
43
FSI.Lib/MVVM/DelegateCommand.cs
Normal file
43
FSI.Lib/MVVM/DelegateCommand.cs
Normal file
@@ -0,0 +1,43 @@
|
||||
using System;
|
||||
using System.Windows.Input;
|
||||
|
||||
namespace FSI.Lib.MVVM
|
||||
{
|
||||
/// <summary>
|
||||
/// DelegateCommand borrowed from
|
||||
/// http://www.wpftutorial.net/DelegateCommand.html
|
||||
/// </summary>
|
||||
public class DelegateCommand : ICommand
|
||||
{
|
||||
private readonly Predicate<object> _canExecute;
|
||||
private readonly Action<object> _execute;
|
||||
|
||||
public DelegateCommand(Action<object> execute,
|
||||
Predicate<object> canExecute = null)
|
||||
{
|
||||
_execute = execute;
|
||||
_canExecute = canExecute;
|
||||
}
|
||||
|
||||
public void RaiseCanExecuteChanged()
|
||||
{
|
||||
CanExecuteChanged?.Invoke(this, EventArgs.Empty);
|
||||
}
|
||||
|
||||
#region ICommand Members
|
||||
|
||||
public event EventHandler CanExecuteChanged;
|
||||
|
||||
public bool CanExecute(object parameter)
|
||||
{
|
||||
return _canExecute == null || _canExecute(parameter);
|
||||
}
|
||||
|
||||
public void Execute(object parameter)
|
||||
{
|
||||
_execute(parameter);
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
77
FSI.Lib/MVVM/RelayCommand.cs
Normal file
77
FSI.Lib/MVVM/RelayCommand.cs
Normal file
@@ -0,0 +1,77 @@
|
||||
using System;
|
||||
using System.Windows.Input;
|
||||
|
||||
namespace FSI.Lib.MVVM
|
||||
{
|
||||
public class RelayCommand<T> : ICommand
|
||||
{
|
||||
#region Fields
|
||||
|
||||
readonly Action<T> _execute = null;
|
||||
readonly Predicate<T> _canExecute = null;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Constructors
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of <see cref="DelegateCommand{T}"/>.
|
||||
/// </summary>
|
||||
/// <param name="execute">Delegate to execute when Execute is called on the command. This can be null to just hook up a CanExecute delegate.</param>
|
||||
/// <remarks><seealso cref="CanExecute"/> will always return true.</remarks>
|
||||
public RelayCommand(Action<T> execute)
|
||||
: this(execute, null)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new command.
|
||||
/// </summary>
|
||||
/// <param name="execute">The execution logic.</param>
|
||||
/// <param name="canExecute">The execution status logic.</param>
|
||||
public RelayCommand(Action<T> execute, Predicate<T> canExecute)
|
||||
{
|
||||
if (execute == null)
|
||||
throw new ArgumentNullException("execute");
|
||||
|
||||
_execute = execute;
|
||||
_canExecute = canExecute;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region ICommand Members
|
||||
|
||||
///<summary>
|
||||
///Defines the method that determines whether the command can execute in its current state.
|
||||
///</summary>
|
||||
///<param name="parameter">Data used by the command. If the command does not require data to be passed, this object can be set to null.</param>
|
||||
///<returns>
|
||||
///true if this command can be executed; otherwise, false.
|
||||
///</returns>
|
||||
public bool CanExecute(object parameter)
|
||||
{
|
||||
return _canExecute == null ? true : _canExecute((T)parameter);
|
||||
}
|
||||
|
||||
///<summary>
|
||||
///Occurs when changes occur that affect whether or not the command should execute.
|
||||
///</summary>
|
||||
public event EventHandler CanExecuteChanged
|
||||
{
|
||||
add { CommandManager.RequerySuggested += value; }
|
||||
remove { CommandManager.RequerySuggested -= value; }
|
||||
}
|
||||
|
||||
///<summary>
|
||||
///Defines the method to be called when the command is invoked.
|
||||
///</summary>
|
||||
///<param name="parameter">Data used by the command. If the command does not require data to be passed, this object can be set to <see langword="null" />.</param>
|
||||
public void Execute(object parameter)
|
||||
{
|
||||
_execute((T)parameter);
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
40
FSI.Lib/MVVM/ViewModel/CurrentTimeViewModel.cs
Normal file
40
FSI.Lib/MVVM/ViewModel/CurrentTimeViewModel.cs
Normal file
@@ -0,0 +1,40 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.Linq;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace FSI.Lib.MVVM.ViewModel
|
||||
{
|
||||
public class CurrentTimeViewModel : INotifyPropertyChanged
|
||||
{
|
||||
private string _currentTime;
|
||||
|
||||
public CurrentTimeViewModel()
|
||||
{
|
||||
UpdateTime();
|
||||
}
|
||||
|
||||
private async void UpdateTime()
|
||||
{
|
||||
CurrentTime = DateTime.Now.ToString("G");
|
||||
await Task.Delay(1000);
|
||||
UpdateTime();
|
||||
}
|
||||
|
||||
public event PropertyChangedEventHandler PropertyChanged;
|
||||
|
||||
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
|
||||
{
|
||||
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
|
||||
}
|
||||
|
||||
public string CurrentTime
|
||||
{
|
||||
get { return _currentTime; }
|
||||
set { _currentTime = value; OnPropertyChanged(); }
|
||||
}
|
||||
}
|
||||
}
|
||||
61
FSI.Lib/MVVM/ViewModelBase.cs
Normal file
61
FSI.Lib/MVVM/ViewModelBase.cs
Normal file
@@ -0,0 +1,61 @@
|
||||
using System.ComponentModel;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
/* Example for use:
|
||||
|
||||
public class MyViewModel : ViewModelBase
|
||||
{
|
||||
private int myProperty;
|
||||
public int MyProperty
|
||||
{
|
||||
get { return myProperty; }
|
||||
set { SetProperty(ref myProperty, value);
|
||||
}
|
||||
}
|
||||
|
||||
*/
|
||||
|
||||
|
||||
namespace FSI.Lib.MVVM
|
||||
{
|
||||
public class ViewModelBase : INotifyPropertyChanged
|
||||
{
|
||||
/// <summary>
|
||||
/// Multicast event for property change notifications.
|
||||
/// </summary>
|
||||
public event PropertyChangedEventHandler PropertyChanged;
|
||||
|
||||
/// <summary>
|
||||
/// Checks if a property already matches the desired value. Sets the property and
|
||||
/// notifies listeners only when necessary.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">Type of the property.</typeparam>
|
||||
/// <param name="storage">Reference to a property with both getter and setter.</param>
|
||||
/// <param name="value">Desired value for the property.</param>
|
||||
/// <param name="propertyName">Name of the property used to notify listeners.This
|
||||
/// value is optional and can be provided automatically when invoked from compilers that
|
||||
/// support CallerMemberName.</param>
|
||||
/// <returns>True if the value was changed, false if the existing value matched the
|
||||
/// desired value.</returns>
|
||||
protected virtual bool SetProperty<T>(ref T storage, T value, [CallerMemberName] string propertyName = null)
|
||||
{
|
||||
if (object.Equals(storage, value)) return false;
|
||||
storage = value;
|
||||
// Log.DebugFormat("{0}.{1} = {2}", this.GetType().Name, propertyName, storage);
|
||||
this.OnPropertyChanged(propertyName);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Notifies listeners that a property value has changed.
|
||||
/// </summary>
|
||||
/// <param name="propertyName">Name of the property used to notify listeners. This
|
||||
/// value is optional and can be provided automatically when invoked from compilers
|
||||
/// that support <see cref="CallerMemberNameAttribute"/>.</param>
|
||||
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
|
||||
{
|
||||
this.PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
|
||||
}
|
||||
}
|
||||
}
|
||||
16
FSI.Lib/TimeStamp.cs
Normal file
16
FSI.Lib/TimeStamp.cs
Normal file
@@ -0,0 +1,16 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace FSI.Lib
|
||||
{
|
||||
public static class TimeStamp
|
||||
{
|
||||
public static string Get(string format)
|
||||
{
|
||||
return DateTime.Now.ToString(format);
|
||||
}
|
||||
}
|
||||
}
|
||||
90
FSI.Lib/WinSettings/ArrayToString.cs
Normal file
90
FSI.Lib/WinSettings/ArrayToString.cs
Normal file
@@ -0,0 +1,90 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
namespace FSI.Lib.WinSettings
|
||||
{
|
||||
internal static class ArrayToString
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents the given array as a single string.
|
||||
/// </summary>
|
||||
/// <param name="array">String array to encode.</param>
|
||||
/// <returns>The encoded string.</returns>
|
||||
public static string Encode(string[] array)
|
||||
{
|
||||
StringBuilder builder = new StringBuilder();
|
||||
|
||||
if (array == null)
|
||||
return string.Empty;
|
||||
|
||||
for (int i = 0; i < array.Length; i++)
|
||||
{
|
||||
if (i > 0)
|
||||
builder.Append(',');
|
||||
if (array[i].IndexOfAny(new[] { ',', '"', '\r', '\n' }) >= 0)
|
||||
builder.Append(string.Format("\"{0}\"", array[i].Replace("\"", "\"\"")));
|
||||
else
|
||||
builder.Append(array[i]);
|
||||
}
|
||||
return builder.ToString();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Decodes a string created with <see cref="Encode"></see> back to an array.
|
||||
/// </summary>
|
||||
/// <param name="s">String to decode.</param>
|
||||
/// <returns>The decoded array.</returns>
|
||||
public static string[] Decode(string s)
|
||||
{
|
||||
List<string> list = new List<string>();
|
||||
int pos = 0;
|
||||
|
||||
if (s == null)
|
||||
return Array.Empty<string>();
|
||||
|
||||
while (pos < s.Length)
|
||||
{
|
||||
if (s[pos] == '\"')
|
||||
{
|
||||
// Parse quoted value
|
||||
StringBuilder builder = new StringBuilder();
|
||||
|
||||
// Skip starting quote
|
||||
pos++;
|
||||
while (pos < s.Length)
|
||||
{
|
||||
if (s[pos] == '"')
|
||||
{
|
||||
// Skip quote
|
||||
pos++;
|
||||
// One quote signifies end of value
|
||||
// Two quote signifies single quote literal
|
||||
if (pos >= s.Length || s[pos] != '"')
|
||||
break;
|
||||
}
|
||||
builder.Append(s[pos++]);
|
||||
}
|
||||
list.Add(builder.ToString());
|
||||
// Skip delimiter
|
||||
pos = s.IndexOf(',', pos);
|
||||
if (pos == -1)
|
||||
pos = s.Length;
|
||||
pos++;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Parse value
|
||||
int start = pos;
|
||||
pos = s.IndexOf(',', pos);
|
||||
if (pos == -1)
|
||||
pos = s.Length;
|
||||
list.Add(s.Substring(start, pos - start));
|
||||
// Skip delimiter
|
||||
pos++;
|
||||
}
|
||||
}
|
||||
return list.ToArray();
|
||||
}
|
||||
}
|
||||
}
|
||||
13
FSI.Lib/WinSettings/EncryptedSettingAttribute.cs
Normal file
13
FSI.Lib/WinSettings/EncryptedSettingAttribute.cs
Normal file
@@ -0,0 +1,13 @@
|
||||
using System;
|
||||
|
||||
namespace FSI.Lib.WinSettings
|
||||
{
|
||||
/// <summary>
|
||||
/// This attribute specifies that this property should be encrypted
|
||||
/// when it is saved.
|
||||
/// </summary>
|
||||
[AttributeUsage(AttributeTargets.Property)]
|
||||
public class EncryptedSettingAttribute : Attribute
|
||||
{
|
||||
}
|
||||
}
|
||||
13
FSI.Lib/WinSettings/ExcludedSettingAttribute.cs
Normal file
13
FSI.Lib/WinSettings/ExcludedSettingAttribute.cs
Normal file
@@ -0,0 +1,13 @@
|
||||
using System;
|
||||
|
||||
namespace FSI.Lib.WinSettings
|
||||
{
|
||||
/// <summary>
|
||||
/// Attribute specifies that this property does not represent a setting and
|
||||
/// should not be saved.
|
||||
/// </summary>
|
||||
[AttributeUsage(AttributeTargets.Property)]
|
||||
public class ExcludedSettingAttribute : Attribute
|
||||
{
|
||||
}
|
||||
}
|
||||
421
FSI.Lib/WinSettings/IniFile.cs
Normal file
421
FSI.Lib/WinSettings/IniFile.cs
Normal file
@@ -0,0 +1,421 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
|
||||
namespace FSI.Lib.WinSettings
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents one name/value pair in an INI file.
|
||||
/// </summary>
|
||||
internal class IniSetting
|
||||
{
|
||||
/// <summary>
|
||||
/// The name of this INI setting.
|
||||
/// </summary>
|
||||
public string Name { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The value of this INI setting.
|
||||
/// </summary>
|
||||
public string Value { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Class to create and read INI files.
|
||||
/// </summary>
|
||||
internal class IniFile
|
||||
{
|
||||
/// <summary>
|
||||
/// Section used for settings not under any section header (within [])
|
||||
/// </summary>
|
||||
public const string DefaultSectionName = "General";
|
||||
|
||||
/// <summary>
|
||||
/// Represents an entire INI file section.
|
||||
/// </summary>
|
||||
private class IniSection
|
||||
{
|
||||
public string Name { get; set; }
|
||||
public Dictionary<string, IniSetting> Settings { get; private set; }
|
||||
|
||||
public IniSection()
|
||||
{
|
||||
Name = string.Empty;
|
||||
Settings = new Dictionary<string, IniSetting>(StringComparer.OrdinalIgnoreCase);
|
||||
}
|
||||
}
|
||||
|
||||
private readonly Dictionary<string, IniSection> Sections = new Dictionary<string, IniSection>(StringComparer.OrdinalIgnoreCase);
|
||||
|
||||
#region File functions
|
||||
|
||||
/// <summary>
|
||||
/// Loads an INI settings file.
|
||||
/// </summary>
|
||||
/// <param name="filename">Path of file to load.</param>
|
||||
public void Load(string filename)
|
||||
{
|
||||
Sections.Clear();
|
||||
|
||||
// Default section
|
||||
IniSection section = new IniSection { Name = DefaultSectionName };
|
||||
Sections.Add(section.Name, section);
|
||||
|
||||
string line;
|
||||
|
||||
#if NET472
|
||||
|
||||
using (StreamReader file = new StreamReader(filename))
|
||||
{
|
||||
while ((line = file.ReadLine()) != null)
|
||||
{
|
||||
line = line.TrimStart();
|
||||
if (line.Length > 0)
|
||||
{
|
||||
if (line[0] == ';')
|
||||
{
|
||||
// Ignore comments
|
||||
}
|
||||
else if (line[0] == '[')
|
||||
{
|
||||
// Parse section header
|
||||
int pos = line.IndexOf(']', 1);
|
||||
if (pos == -1)
|
||||
pos = line.Length;
|
||||
|
||||
string name = line.Substring(1, pos - 1).Trim();
|
||||
|
||||
if (name.Length > 0)
|
||||
{
|
||||
if (!Sections.TryGetValue(name, out section))
|
||||
{
|
||||
section = new IniSection { Name = name };
|
||||
Sections.Add(section.Name, section);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Parse setting name and value
|
||||
string name, value;
|
||||
|
||||
int pos = line.IndexOf('=');
|
||||
if (pos == -1)
|
||||
{
|
||||
name = line.Trim();
|
||||
value = string.Empty;
|
||||
}
|
||||
else
|
||||
{
|
||||
name = line.Substring(0, pos).Trim();
|
||||
value = line.Substring(pos + 1);
|
||||
}
|
||||
|
||||
if (name.Length > 0)
|
||||
{
|
||||
if (section.Settings.TryGetValue(name, out IniSetting setting))
|
||||
{
|
||||
setting.Value = value;
|
||||
}
|
||||
else
|
||||
{
|
||||
setting = new IniSetting { Name = name, Value = value };
|
||||
section.Settings.Add(name, setting);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
#elif NET6_0_OR_GREATER
|
||||
|
||||
using StreamReader file = new StreamReader(filename);
|
||||
|
||||
while ((line = file.ReadLine()) != null)
|
||||
{
|
||||
line = line.TrimStart();
|
||||
if (line.Length > 0)
|
||||
{
|
||||
if (line[0] == ';')
|
||||
{
|
||||
// Ignore comments
|
||||
}
|
||||
else if (line[0] == '[')
|
||||
{
|
||||
// Parse section header
|
||||
int pos = line.IndexOf(']', 1);
|
||||
if (pos == -1)
|
||||
pos = line.Length;
|
||||
|
||||
string name = line.Substring(1, pos - 1).Trim();
|
||||
|
||||
if (name.Length > 0)
|
||||
{
|
||||
if (!Sections.TryGetValue(name, out section))
|
||||
{
|
||||
section = new IniSection { Name = name };
|
||||
Sections.Add(section.Name, section);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Parse setting name and value
|
||||
string name, value;
|
||||
|
||||
int pos = line.IndexOf('=');
|
||||
if (pos == -1)
|
||||
{
|
||||
name = line.Trim();
|
||||
value = string.Empty;
|
||||
}
|
||||
else
|
||||
{
|
||||
name = line.Substring(0, pos).Trim();
|
||||
value = line.Substring(pos + 1);
|
||||
}
|
||||
|
||||
if (name.Length > 0)
|
||||
{
|
||||
if (section.Settings.TryGetValue(name, out IniSetting setting))
|
||||
{
|
||||
setting.Value = value;
|
||||
}
|
||||
else
|
||||
{
|
||||
setting = new IniSetting { Name = name, Value = value };
|
||||
section.Settings.Add(name, setting);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Writes the current settings to an INI file. If the file already exists, it is overwritten.
|
||||
/// </summary>
|
||||
/// <param name="filename">Path of file to write to.</param>
|
||||
public void Save(string filename)
|
||||
{
|
||||
#if NET472
|
||||
using (StreamWriter file = new StreamWriter(filename, false))
|
||||
{
|
||||
bool firstLine = true;
|
||||
foreach (IniSection section in Sections.Values)
|
||||
{
|
||||
if (firstLine)
|
||||
firstLine = false;
|
||||
else
|
||||
file.WriteLine();
|
||||
|
||||
if (section.Settings.Any())
|
||||
{
|
||||
file.WriteLine("[{0}]", section.Name);
|
||||
foreach (IniSetting setting in section.Settings.Values)
|
||||
file.WriteLine("{0}={1}", setting.Name, setting.Value);
|
||||
}
|
||||
}
|
||||
}
|
||||
#elif NET6_0_OR_GREATER
|
||||
|
||||
using StreamWriter file = new StreamWriter(filename, false);
|
||||
|
||||
bool firstLine = true;
|
||||
foreach (IniSection section in Sections.Values)
|
||||
{
|
||||
if (firstLine)
|
||||
firstLine = false;
|
||||
else
|
||||
file.WriteLine();
|
||||
|
||||
if (section.Settings.Any())
|
||||
{
|
||||
file.WriteLine("[{0}]", section.Name);
|
||||
foreach (IniSetting setting in section.Settings.Values)
|
||||
file.WriteLine("{0}={1}", setting.Name, setting.Value);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Read values
|
||||
|
||||
/// <summary>
|
||||
/// Returns the value of an INI setting.
|
||||
/// </summary>
|
||||
/// <param name="section">The INI file section.</param>
|
||||
/// <param name="setting">The INI setting name.</param>
|
||||
/// <param name="defaultValue">The value to return if the setting was not found.</param>
|
||||
/// <returns>Returns the specified setting value.</returns>
|
||||
public string GetSetting(string section, string setting, string defaultValue = null)
|
||||
{
|
||||
if (Sections.TryGetValue(section, out IniSection iniSection))
|
||||
{
|
||||
if (iniSection.Settings.TryGetValue(setting, out IniSetting iniSetting))
|
||||
return iniSetting.Value;
|
||||
}
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the value of an INI setting as an integer value.
|
||||
/// </summary>
|
||||
/// <param name="section">The INI file section.</param>
|
||||
/// <param name="setting">The INI setting name.</param>
|
||||
/// <param name="defaultValue">The value to return if the setting was not found,
|
||||
/// or if it could not be converted to a integer value.</param>
|
||||
/// <returns>Returns the specified setting value as an integer value.</returns>
|
||||
public int GetSetting(string section, string setting, int defaultValue)
|
||||
{
|
||||
if (int.TryParse(GetSetting(section, setting), out int value))
|
||||
return value;
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the value of an INI setting as a double value.
|
||||
/// </summary>
|
||||
/// <param name="section">The INI file section.</param>
|
||||
/// <param name="setting">The INI setting name.</param>
|
||||
/// <param name="defaultValue">The value to return if the setting was not found,
|
||||
/// or if it could not be converted to a double value.</param>
|
||||
/// <returns>Returns the specified setting value as a double value.</returns>
|
||||
public double GetSetting(string section, string setting, double defaultValue)
|
||||
{
|
||||
if (double.TryParse(GetSetting(section, setting), out double value))
|
||||
return value;
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the value of an INI setting as a Boolean value.
|
||||
/// </summary>
|
||||
/// <param name="section">The INI file section.</param>
|
||||
/// <param name="setting">The INI setting name.</param>
|
||||
/// <param name="defaultValue">The value to return if the setting was not found,
|
||||
/// or if it could not be converted to a Boolean value.</param>
|
||||
/// <returns>Returns the specified setting value as a Boolean.</returns>
|
||||
public bool GetSetting(string section, string setting, bool defaultValue)
|
||||
{
|
||||
if (ConvertToBool(GetSetting(section, setting), out bool value))
|
||||
return value;
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns all settings in the given INI section.
|
||||
/// </summary>
|
||||
/// <param name="section">The section that contains the settings to be retrieved.</param>
|
||||
/// <returns>Returns the settings in the given INI section.</returns>
|
||||
public IEnumerable<IniSetting> GetSectionSettings(string section)
|
||||
{
|
||||
if (Sections.TryGetValue(section, out IniSection iniSection))
|
||||
{
|
||||
foreach (var setting in iniSection.Settings)
|
||||
yield return setting.Value;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Write values
|
||||
|
||||
/// <summary>
|
||||
/// Sets an INI file setting. The setting is not written to disk until
|
||||
/// <see cref="Save"/> is called.
|
||||
/// </summary>
|
||||
/// <param name="section">The INI-file section.</param>
|
||||
/// <param name="setting">The name of the INI-file setting.</param>
|
||||
/// <param name="value">The value of the INI-file setting</param>
|
||||
public void SetSetting(string section, string setting, string value)
|
||||
{
|
||||
if (!Sections.TryGetValue(section, out IniSection iniSection))
|
||||
{
|
||||
iniSection = new IniSection { Name = section };
|
||||
Sections.Add(iniSection.Name, iniSection);
|
||||
}
|
||||
if (!iniSection.Settings.TryGetValue(setting, out IniSetting iniSetting))
|
||||
{
|
||||
iniSetting = new IniSetting { Name = setting };
|
||||
iniSection.Settings.Add(iniSetting.Name, iniSetting);
|
||||
}
|
||||
iniSetting.Value = value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets an INI file setting with an integer value.
|
||||
/// </summary>
|
||||
/// <param name="section">The INI-file section.</param>
|
||||
/// <param name="setting">The name of the INI-file setting.</param>
|
||||
/// <param name="value">The value of the INI-file setting</param>
|
||||
public void SetSetting(string section, string setting, int value)
|
||||
{
|
||||
SetSetting(section, setting, value.ToString());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets an INI file setting with a double value.
|
||||
/// </summary>
|
||||
/// <param name="section">The INI-file section.</param>
|
||||
/// <param name="setting">The name of the INI-file setting.</param>
|
||||
/// <param name="value">The value of the INI-file setting</param>
|
||||
public void SetSetting(string section, string setting, double value)
|
||||
{
|
||||
SetSetting(section, setting, value.ToString());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets an INI file setting with a Boolean value.
|
||||
/// </summary>
|
||||
/// <param name="section">The INI-file section.</param>
|
||||
/// <param name="setting">The name of the INI-file setting.</param>
|
||||
/// <param name="value">The value of the INI-file setting</param>
|
||||
public void SetSetting(string section, string setting, bool value)
|
||||
{
|
||||
SetSetting(section, setting, value.ToString());
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Boolean parsing
|
||||
|
||||
private readonly string[] TrueStrings = { "true", "yes", "on" };
|
||||
private readonly string[] FalseStrings = { "false", "no", "off" };
|
||||
|
||||
private bool ConvertToBool(string s, out bool value)
|
||||
{
|
||||
if (s == null)
|
||||
value = false;
|
||||
if (TrueStrings.Any(s2 => string.Compare(s, s2, true) == 0))
|
||||
value = true;
|
||||
else if (FalseStrings.Any(s2 => string.Compare(s, s2, true) == 0))
|
||||
value = false;
|
||||
else if (int.TryParse(s, out int i))
|
||||
value = i != 0;
|
||||
else
|
||||
{
|
||||
value = false;
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
//public void Dump()
|
||||
//{
|
||||
// foreach (IniSection section in Sections.Values)
|
||||
// {
|
||||
// Debug.WriteLine(string.Format("[{0}]", section.Name));
|
||||
// foreach (IniSetting setting in section.Settings.Values)
|
||||
// Debug.WriteLine("[{0}]=[{1}]", setting.Name, setting.Value);
|
||||
// }
|
||||
//}
|
||||
|
||||
}
|
||||
}
|
||||
142
FSI.Lib/WinSettings/IniSettings.cs
Normal file
142
FSI.Lib/WinSettings/IniSettings.cs
Normal file
@@ -0,0 +1,142 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
|
||||
namespace FSI.Lib.WinSettings
|
||||
{
|
||||
/// <summary>
|
||||
/// The <see cref="IniSettings"/> class makes it very easy to save your application
|
||||
/// settings to an INI file.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>
|
||||
/// To use the class, simply derive your own settings class from
|
||||
/// <see cref="IniSettings" /> and add the public properties that you want to be
|
||||
/// saved as settings. You can then call the <see cref="Settings.Load"/> and
|
||||
/// <see cref="Settings.Save"/> methods to read or write those settings to an INI
|
||||
/// file.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// Your derived class' constructor should initialize your settings properties to
|
||||
/// their default values.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// Two attributes are available for public properties in your derived class. The
|
||||
/// first is <see cref="EncryptedSettingAttribute" />. Use this attribute if you
|
||||
/// want the setting to be encrypted when saved to file. When using this attribute on
|
||||
/// any property, you must provide a valid encryption password to the
|
||||
/// <see cref="IniSettings" /> constructor.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// The second is the <see cref="ExcludedSettingAttribute"/>. Use this attribute
|
||||
/// on any properties that are used internally by your code and should not saved to
|
||||
/// file.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// All public properties without the <see cref="ExcludedSettingAttribute"></see>
|
||||
/// attribute must be of one of the supported data types. This includes all the basic
|
||||
/// data types as well as <see cref="string[]"></see> and <see cref="byte[]"></see>.
|
||||
/// All other types will raise an exception. In addition, INI files do not support
|
||||
/// strings that contain newlines unless those strings are encrypted.
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
/// <example>
|
||||
/// The following example creates a settings class called <c>MySettings</c> with
|
||||
/// several properties, two of which are encrypted when saved to file.
|
||||
/// <code>
|
||||
/// public class MySettings : IniSettings
|
||||
/// {
|
||||
/// // Define properties to be saved to file
|
||||
/// public string EmailHost { get; set; }
|
||||
/// public int EmailPort { get; set; }
|
||||
///
|
||||
/// // The following properties will be encrypted
|
||||
/// [EncryptedSetting]
|
||||
/// public string UserName { get; set; }
|
||||
/// [EncryptedSetting]
|
||||
/// public string Password { get; set; }
|
||||
///
|
||||
/// // The following property will not be saved to file
|
||||
/// // Non-public properties are also not saved to file
|
||||
/// [ExcludedSetting]
|
||||
/// public DateTime Created { get; set; }
|
||||
///
|
||||
/// public MySettings(string filename)
|
||||
/// : base(filename, "Password123")
|
||||
/// {
|
||||
/// // Set initial, default property values
|
||||
/// EmailHost = string.Empty;
|
||||
/// EmailPort = 0;
|
||||
/// UserName = string.Empty;
|
||||
/// Password = string.Empty;
|
||||
///
|
||||
/// Created = DateTime.Now;
|
||||
/// }
|
||||
/// }
|
||||
/// </code>
|
||||
/// </example>
|
||||
/// <seealso cref="RegistrySettings"/>
|
||||
/// <seealso cref="XmlSettings"/>
|
||||
public abstract class IniSettings : Settings
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets the name of the INI settings file.
|
||||
/// </summary>
|
||||
[ExcludedSetting]
|
||||
public string FileName { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Constructs an instance of the <c>XmlSettings</c> class.
|
||||
/// </summary>
|
||||
/// <param name="filename">Name of the settings file.</param>
|
||||
/// <param name="password">Encryption password. May be <c>null</c>
|
||||
/// if no settings use the <see cref="EncryptedSettingAttribute" />
|
||||
/// attribute.</param>
|
||||
public IniSettings(string filename, string password = null)
|
||||
: base(password)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(filename))
|
||||
throw new ArgumentException("A valid path and file name is required.", nameof(filename));
|
||||
FileName = filename;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Performs internal load operations.
|
||||
/// </summary>
|
||||
/// <param name="settings">Settings to be loaded.</param>
|
||||
public override void OnLoadSettings(IEnumerable<Setting> settings)
|
||||
{
|
||||
if (File.Exists(FileName))
|
||||
{
|
||||
// Load INI file
|
||||
IniFile iniFile = new IniFile();
|
||||
iniFile.Load(FileName);
|
||||
// Read settings
|
||||
foreach (Setting setting in settings)
|
||||
{
|
||||
string value = iniFile.GetSetting(IniFile.DefaultSectionName, setting.Name, null);
|
||||
if (value != null)
|
||||
setting.SetValueFromString(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Performs internal save operations.
|
||||
/// </summary>
|
||||
/// <param name="settings">Settings to be saved.</param>
|
||||
public override void OnSaveSettings(IEnumerable<Setting> settings)
|
||||
{
|
||||
// Create INI file
|
||||
IniFile iniFile = new IniFile();
|
||||
// Write settings
|
||||
foreach (Setting setting in settings)
|
||||
{
|
||||
string value = setting.GetValueAsString();
|
||||
if (value != null)
|
||||
iniFile.SetSetting(IniFile.DefaultSectionName, setting.Name, value);
|
||||
}
|
||||
iniFile.Save(FileName);
|
||||
}
|
||||
}
|
||||
}
|
||||
180
FSI.Lib/WinSettings/Readme.md
Normal file
180
FSI.Lib/WinSettings/Readme.md
Normal file
@@ -0,0 +1,180 @@
|
||||
# WinSettings
|
||||
|
||||
[](https://www.nuget.org/packages/SoftCircuits.WinSettings/)
|
||||
|
||||
```
|
||||
Install-Package SoftCircuits.WinSettings
|
||||
```
|
||||
|
||||
## Overview
|
||||
|
||||
WinSettings is a .NET class library that makes it easy to save and retrieve application settings on Windows. It includes three settings classes: `IniSettings`, which stores the settings to an INI file; `XmlSettings`, which stores the settings to an XML file, and `RegistrySettings`, which stores the settings to the Windows registry. In addition, it makes it easy to define your own settings type.
|
||||
|
||||
Settings can be encrypted just by adding a property attribute. There is also an attribute to exclude a particular property when the property is used internally and does not represent an application setting.
|
||||
|
||||
To use a settings class, simply derive your own settings class from one of the ones described above and add public properties that you want to be saved. Your class' constructor should set any default values. Then call the `Save()` and `Load()` methods to save the settings in your class.
|
||||
|
||||
## IniSettings Class
|
||||
|
||||
The <see cref="IniSettings"/> class makes it very easy to save your application settings to an INI file.
|
||||
|
||||
To use the class, simply derive your own settings class from `IniSettings` and add the public properties that you want to be saved as settings. You can then call the `Load()` and `Save()` methods to read or write those settings to an INI file.
|
||||
|
||||
Your derived class' constructor should initialize your settings properties to their default values.
|
||||
|
||||
Two attributes are available for public properties in your derived class. The first is `EncryptedSettingAttribute`. Use this attribute if you want the setting to be encrypted when saved to file. When using this attribute on any property, you must provide a valid encryption password to the `IniSettings` constructor.
|
||||
|
||||
The second is the `ExcludedSettingAttribute`. Use this attribute on any properties that are used internally by your code and should not saved to file.
|
||||
|
||||
All public properties without the `ExcludedSettingAttribute` attribute must be of one of the supported data types. This includes all the basic data types as well as `string[]` and `byte[]`. All other types will raise an exception. In addition, INI files do not support strings that contain newlines unless those strings are encrypted.
|
||||
|
||||
#### Example
|
||||
|
||||
The following example creates a settings class called `MySettings` with several properties, two of which are encrypted when saved to file.
|
||||
|
||||
```cs
|
||||
public class MySettings : IniSettings
|
||||
{
|
||||
// Define properties to be saved to file
|
||||
public string EmailHost { get; set; }
|
||||
public int EmailPort { get; set; }
|
||||
|
||||
// The following properties will be encrypted
|
||||
[EncryptedSetting]
|
||||
public string UserName { get; set; }
|
||||
[EncryptedSetting]
|
||||
public string Password { get; set; }
|
||||
|
||||
// The following property will not be saved to file
|
||||
// Non-public properties are also not saved to file
|
||||
[ExcludedSetting]
|
||||
public DateTime Created { get; set; }
|
||||
|
||||
public MySettings(string filename)
|
||||
: base(filename, "Password123")
|
||||
{
|
||||
// Set initial, default property values
|
||||
EmailHost = string.Empty;
|
||||
EmailPort = 0;
|
||||
UserName = string.Empty;
|
||||
Password = string.Empty;
|
||||
|
||||
Created = DateTime.Now;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## XmlSettings Class
|
||||
|
||||
The <see `XmlSettings` class makes it very easy to save your application settings to an XML file.
|
||||
|
||||
To use the class, simply derive your own settings class from `XmlSettings` and add the public properties that you want to be saved as settings. You can then call the `Load()` and `Save()` methods to read or write those settings to an XML file.
|
||||
|
||||
Your derived class' constructor should initialize your settings properties to their default values.
|
||||
|
||||
Two attributes are available for public properties in your derived class. The first is `EncryptedSettingAttribute`. Use this attribute if you want the setting to be encrypted when saved to file. When using this attribute on any property, you must provide a valid encryption password to the `XmlSettings` constructor.
|
||||
|
||||
The second is the `ExcludedSettingAttribute` Use this attribute on any properties that are used internally by your code and should not saved to file.
|
||||
|
||||
All public properties without the `ExcludedSettingAttribute` attribute must be of one of the supported data types. This includes all the basic data types `string[]` and `byte[]`. All other types will raise an exception.
|
||||
|
||||
#### Example
|
||||
|
||||
The following example creates a settings class called `MySettings` with several properties, two of which are encrypted when saved to file.
|
||||
|
||||
```cs
|
||||
public class MySettings : XmlSettings
|
||||
{
|
||||
// Define properties to be saved to file
|
||||
public string EmailHost { get; set; }
|
||||
public int EmailPort { get; set; }
|
||||
|
||||
// The following properties will be encrypted
|
||||
[EncryptedSetting]
|
||||
public string UserName { get; set; }
|
||||
[EncryptedSetting]
|
||||
public string Password { get; set; }
|
||||
|
||||
// The following property will not be saved to file
|
||||
// Non-public properties are also not saved to file
|
||||
[ExcludedSetting]
|
||||
public DateTime Created { get; set; }
|
||||
|
||||
public MySettings(string filename)
|
||||
: base(filename, "Password123")
|
||||
{
|
||||
// Set initial, default property values
|
||||
EmailHost = string.Empty;
|
||||
EmailPort = 0;
|
||||
UserName = string.Empty;
|
||||
Password = string.Empty;
|
||||
|
||||
Created = DateTime.Now;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## RegistrySettings Class
|
||||
|
||||
The `RegistrySettings` class makes it very easy to save your application settings to the system registry.
|
||||
|
||||
To use the class, simply derive your own settings class from `RegistrySettings` and add the public properties that you want to be
|
||||
saved as settings. You can then call the `Load()` and `Save()` methods to read or write those settings to the system registry.
|
||||
|
||||
Your derived class' constructor should initialize your settings properties to their default values.
|
||||
|
||||
Two attributes are available for public properties in your derived class. The first is `EncryptedSettingAttribute`. Use this attribute if you want the setting to be encrypted when saved to file. When using this attribute on any property, you must provide a valid encryption password to the `RegistrySettings` constructor.
|
||||
|
||||
The second is the `ExcludedSettingAttribute`. Use this attribute on any properties that are used internally by your code and should not saved to the registry.
|
||||
|
||||
All public properties without the `ExcludedSettingAttribute` attribute must be of one of the supported data types. This includes all the basic data types as well as `string[]` and `byte[]`. All other types will raise an exception.
|
||||
|
||||
#### Example
|
||||
|
||||
The following example creates a settings class called `MySettings` with several properties, two of which are encrypted when saved to file.
|
||||
|
||||
```cs
|
||||
public class MySettings : RegistrySettings
|
||||
{
|
||||
// Define properties to be saved to file
|
||||
public string EmailHost { get; set; }
|
||||
public int EmailPort { get; set; }
|
||||
|
||||
// The following properties will be encrypted
|
||||
[EncryptedSetting]
|
||||
public string UserName { get; set; }
|
||||
[EncryptedSetting]
|
||||
public string Password { get; set; }
|
||||
|
||||
// The following property will not be saved to file
|
||||
// Non-public properties are also not saved to file
|
||||
[ExcludedSetting]
|
||||
public DateTime Created { get; set; }
|
||||
|
||||
public MySettings(string companyName, string applicationName, RegistrySettingsType settingsType)
|
||||
: base(companyName, applicationName, settingsType, "Password123")
|
||||
{
|
||||
// Set initial, default property values
|
||||
EmailHost = string.Empty;
|
||||
EmailPort = 0;
|
||||
UserName = string.Empty;
|
||||
Password = string.Empty;
|
||||
|
||||
Created = DateTime.Now;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Settings Class
|
||||
|
||||
The `Settings` class is the base class for the `IniSettings`, `XmlSettings` and `RegistrySettings` classes. You don't need this class but you could use it to create your own type of custom settings class.
|
||||
|
||||
To do this, create your own `static`, `abstract` class that derives from `Settings` and override the virtual `OnSaveSettings()` and `OnLoadSettings()` methods.
|
||||
|
||||
As the name suggests, `OnSaveSettings()` is called when the settings are being saved. This method is passed a collection of `Setting` objects. Your handler needs to write these settings to your custom data store. The `Setting.Name` property contains the setting name. Use the `Setting.GetValue()` method to get the value. Or use the `Setting.GetValueAsString()` instead if your data store only supports string values.
|
||||
|
||||
The steps to override `OnLoadSettings()` is similar. This method is also passed a collection of `Setting` objects. Your handler needs to read each named setting from your custom data store. You can then set that value using the `Setting.SetValue()` or `Setting.SetValueFromString()` methods.
|
||||
|
||||
## Dependencies
|
||||
|
||||
This project requires the NuGet packages [SoftCircuits.EasyEncryption](https://www.nuget.org/packages/SoftCircuits.EasyEncryption/) and Microsoft.Win32.Registry.
|
||||
154
FSI.Lib/WinSettings/RegistrySettings.cs
Normal file
154
FSI.Lib/WinSettings/RegistrySettings.cs
Normal file
@@ -0,0 +1,154 @@
|
||||
using Microsoft.Win32;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace FSI.Lib.WinSettings
|
||||
{
|
||||
/// <summary>
|
||||
/// Specifies the location within the registry where <see cref="RegistrySettings"/> stores
|
||||
/// settings.
|
||||
/// </summary>
|
||||
public enum RegistrySettingsType
|
||||
{
|
||||
/// <summary>
|
||||
/// Stores settings in the Windows registry base key HKEY_CURRENT_USER, normally
|
||||
/// used for storing information about the current user preferences.
|
||||
/// </summary>
|
||||
CurrentUser,
|
||||
|
||||
/// <summary>
|
||||
/// Stores settings in thee Windows registry base key HKEY_LOCAL_MACHINE, normally
|
||||
/// use for storing configuration data for the local machine.
|
||||
/// </summary>
|
||||
LocalMachine
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="RegistrySettings"/> class makes it very easy to save your application
|
||||
/// settings to the system registry.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>
|
||||
/// To use the class, simply derive your own settings class from
|
||||
/// <see cref="RegistrySettings" /> and add the public properties that you want to be
|
||||
/// saved as settings. You can then call the <see cref="Settings.Load"/> and
|
||||
/// <see cref="Settings.Save"/> methods to read or write those settings to the
|
||||
/// system registry.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// Your derived class' constructor should initialize your settings properties to
|
||||
/// their default values.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// Two attributes are available for public properties in your derived class. The
|
||||
/// first is <see cref="EncryptedSettingAttribute" />. Use this attribute if you
|
||||
/// want the setting to be encrypted when saved to file. When using this attribute on
|
||||
/// any property, you must provide a valid encryption password to the
|
||||
/// <see cref="RegistrySettings" /> constructor.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// The second is the <see cref="ExcludedSettingAttribute"/>. Use this attribute
|
||||
/// on any properties that are used internally by your code and should not saved to
|
||||
/// the registry.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// All public properties without the <see cref="ExcludedSettingAttribute"></see>
|
||||
/// attribute must be of one of the supported data types. This includes all the basic
|
||||
/// data types as well as <see cref="string[]"></see> and <see cref="byte[]"></see>.
|
||||
/// All other types will raise an exception.
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
/// <example>
|
||||
/// The following example creates a settings class called <c>MySettings</c> with
|
||||
/// several properties, two of which are encrypted when saved to file.
|
||||
/// <code>
|
||||
/// public class MySettings : RegistrySettings
|
||||
/// {
|
||||
/// // Define properties to be saved to file
|
||||
/// public string EmailHost { get; set; }
|
||||
/// public int EmailPort { get; set; }
|
||||
///
|
||||
/// // The following properties will be encrypted
|
||||
/// [EncryptedSetting]
|
||||
/// public string UserName { get; set; }
|
||||
/// [EncryptedSetting]
|
||||
/// public string Password { get; set; }
|
||||
///
|
||||
/// // The following property will not be saved to file
|
||||
/// // Non-public properties are also not saved to file
|
||||
/// [ExcludedSetting]
|
||||
/// public DateTime Created { get; set; }
|
||||
///
|
||||
/// public MySettings(string companyName, string applicationName, RegistrySettingsType settingsType)
|
||||
/// : base(companyName, applicationName, settingsType, "Password123")
|
||||
/// {
|
||||
/// // Set initial, default property values
|
||||
/// EmailHost = string.Empty;
|
||||
/// EmailPort = 0;
|
||||
/// UserName = string.Empty;
|
||||
/// Password = string.Empty;
|
||||
///
|
||||
/// Created = DateTime.Now;
|
||||
/// }
|
||||
/// }
|
||||
/// </code>
|
||||
/// </example>
|
||||
/// <seealso cref="IniSettings"/>
|
||||
/// <seealso cref="XmlSettings"/>
|
||||
public abstract class RegistrySettings : Settings
|
||||
{
|
||||
private readonly string SubKeyPath;
|
||||
private readonly RegistryKey RegistryKey;
|
||||
|
||||
/// <summary>
|
||||
/// Constructs a new <c>RegistrySettings</c> instance.
|
||||
/// </summary>
|
||||
/// <param name="companyName">Company name entry in registry.</param>
|
||||
/// <param name="applicationName">Application name entry in registration.</param>
|
||||
/// <param name="settingsType">Section to store entries in registry.</param>
|
||||
/// <param name="password">Encryption password. May be <c>null</c> if no settings
|
||||
/// use the <see cref="EncryptedSettingAttribute" /> attribute.</param>
|
||||
public RegistrySettings(string companyName, string applicationName, RegistrySettingsType settingsType, string password = null)
|
||||
: base(password)
|
||||
{
|
||||
SubKeyPath = string.Format("Software\\{0}\\{1}", companyName, applicationName);
|
||||
RegistryKey = settingsType == RegistrySettingsType.CurrentUser ? Registry.CurrentUser : Registry.LocalMachine;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Performs internal save operations.
|
||||
/// </summary>
|
||||
/// <param name="settings">Settings to be saved.</param>
|
||||
public override void OnSaveSettings(IEnumerable<Setting> settings)
|
||||
{
|
||||
using (RegistryKey registryKey = RegistryKey.CreateSubKey(SubKeyPath, RegistryKeyPermissionCheck.ReadWriteSubTree))
|
||||
{
|
||||
foreach (var setting in settings)
|
||||
{
|
||||
var value = setting.GetValue();
|
||||
if (value != null)
|
||||
registryKey.SetValue(setting.Name, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Performs internal load operations.
|
||||
/// </summary>
|
||||
/// <param name="settings">Settings to be loaded.</param>
|
||||
public override void OnLoadSettings(IEnumerable<Setting> settings)
|
||||
{
|
||||
using (RegistryKey registryKey = RegistryKey.OpenSubKey(SubKeyPath))
|
||||
{
|
||||
if (registryKey != null)
|
||||
{
|
||||
foreach (var setting in settings)
|
||||
{
|
||||
object value = registryKey.GetValue(setting.Name);
|
||||
if (value != null)
|
||||
setting.SetValue(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
117
FSI.Lib/WinSettings/Setting.cs
Normal file
117
FSI.Lib/WinSettings/Setting.cs
Normal file
@@ -0,0 +1,117 @@
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
|
||||
namespace FSI.Lib.WinSettings
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a single setting for <see cref="Settings"></see>-derived classes.
|
||||
/// </summary>
|
||||
public class Setting
|
||||
{
|
||||
private readonly Settings Settings;
|
||||
private readonly PropertyInfo PropertyInfo;
|
||||
private readonly bool Encrypted;
|
||||
|
||||
/// <summary>
|
||||
/// Constructs a new instance of the <see cref="Setting"></see> class.
|
||||
/// </summary>
|
||||
/// <param name="settings">The <see cref="Settings"/> class that contains
|
||||
/// this property (setting).</param>
|
||||
/// <param name="propertyInfo">The <see cref="PropertyInfo"></see> for this
|
||||
/// property.</param>
|
||||
/// <param name="encrypted">Indicates whether or not this setting is
|
||||
/// encrypted.</param>
|
||||
public Setting(Settings settings, PropertyInfo propertyInfo, bool encrypted)
|
||||
{
|
||||
Settings = settings;
|
||||
PropertyInfo = propertyInfo;
|
||||
Encrypted = encrypted;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the name of this setting.
|
||||
/// </summary>
|
||||
public string Name => PropertyInfo.Name;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the type of this setting.
|
||||
/// </summary>
|
||||
public Type Type => Encrypted ? typeof(string) : PropertyInfo.PropertyType;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the value of this setting.
|
||||
/// </summary>
|
||||
/// <returns>Returns the value of this setting.</returns>
|
||||
public object GetValue()
|
||||
{
|
||||
object value = PropertyInfo.GetValue(Settings);
|
||||
if (value != null && Encrypted && Settings.Encryption != null)
|
||||
return Settings.Encryption.Encrypt(value);
|
||||
return value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the value of this setting.
|
||||
/// </summary>
|
||||
/// <param name="value">The value this setting should be set to.</param>
|
||||
public void SetValue(object value)
|
||||
{
|
||||
// Leave property value unmodified if no value or error
|
||||
if (value != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (Encrypted && Settings.Encryption != null)
|
||||
{
|
||||
// Ecrypted values stored as string
|
||||
if (value is string s)
|
||||
PropertyInfo.SetValue(Settings, Settings.Encryption.Decrypt(s, PropertyInfo.PropertyType));
|
||||
}
|
||||
else PropertyInfo.SetValue(Settings, Convert.ChangeType(value, Type));
|
||||
}
|
||||
catch (Exception) { Debug.Assert(false); }
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets this setting's value as a string.
|
||||
/// </summary>
|
||||
/// <returns>Returns this setting's value as a string.</returns>
|
||||
public string GetValueAsString()
|
||||
{
|
||||
object value = GetValue();
|
||||
|
||||
if (value == null)
|
||||
return string.Empty;
|
||||
else if (value is byte[] byteArray)
|
||||
return ArrayToString.Encode(byteArray.Select(b => b.ToString()).ToArray());
|
||||
else if (value is string[] stringArray)
|
||||
return ArrayToString.Encode(stringArray);
|
||||
else
|
||||
return value?.ToString() ?? string.Empty;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets this setting's value from a string.
|
||||
/// </summary>
|
||||
/// <param name="value">A string that represents the value this setting should be set to.</param>
|
||||
public void SetValueFromString(string value)
|
||||
{
|
||||
if (value != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (Type == typeof(byte[]))
|
||||
SetValue(ArrayToString.Decode(value).Select(s => byte.Parse(s)).ToArray());
|
||||
else if (Type == typeof(string[]))
|
||||
SetValue(ArrayToString.Decode(value));
|
||||
else
|
||||
SetValue(value);
|
||||
}
|
||||
catch (Exception) { Debug.Assert(false); }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
106
FSI.Lib/WinSettings/Settings.cs
Normal file
106
FSI.Lib/WinSettings/Settings.cs
Normal file
@@ -0,0 +1,106 @@
|
||||
using FSI.Lib.EasyEncryption;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Reflection;
|
||||
|
||||
namespace FSI.Lib.WinSettings
|
||||
{
|
||||
/// <summary>
|
||||
/// Provides an abstract base class for specialized settings classes.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>
|
||||
/// Specialized classes should inherit from this class and implement specific storage and
|
||||
/// retrieval logic for each settings by overriding the
|
||||
/// <see cref="OnSaveSettings(IEnumerable{Setting})"/> and
|
||||
/// <see cref="OnLoadSettings(IEnumerable{Setting})"/> methods.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// Ultimately, the specialized classes will then be overridden by each application's
|
||||
/// settings class. The public properties in that class will become the settings that
|
||||
/// are saved by classes that derive from this class.
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
public abstract class Settings
|
||||
{
|
||||
private IEnumerable<Setting> SettingsList { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Abstract method called when the settings should be saved. Allows
|
||||
/// the derived class to save those settings in a specialized way.
|
||||
/// </summary>
|
||||
/// <param name="settings">The list of settings to be saved.</param>
|
||||
public abstract void OnSaveSettings(IEnumerable<Setting> settings);
|
||||
|
||||
/// <summary>
|
||||
/// Abstract method called when the settings sould be loaded. Allows
|
||||
/// the derived class to load those settings in a specialized way.
|
||||
/// </summary>
|
||||
/// <param name="settings">The list of settings to be loaded.</param>
|
||||
public abstract void OnLoadSettings(IEnumerable<Setting> settings);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the <c>Encryption</c> instance associated with this <c>Settings</c>
|
||||
/// instance.
|
||||
/// </summary>
|
||||
[ExcludedSetting]
|
||||
public Encryption Encryption { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Constructs a new <see cref="Settings"></see> instance.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// An exception is thrown if password is null but one or more properties have the
|
||||
/// <see cref="EncryptedSettingAttribute"></see> attribute.
|
||||
/// </remarks>
|
||||
/// <param name="password">Encryption password. Can be <c>null</c> if no
|
||||
/// properties have the <see cref="EncryptedSettingAttribute"></see>
|
||||
/// attribute.</param>
|
||||
public Settings(string password = null)
|
||||
{
|
||||
if (password == null)
|
||||
password = GetType().Namespace.ToString();
|
||||
|
||||
SettingsList = BuildSettingsList();
|
||||
Encryption = password != null ?
|
||||
new Encryption(password, EncryptionAlgorithm.TripleDes) :
|
||||
null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Saves all settings.
|
||||
/// </summary>
|
||||
public void Save()
|
||||
{
|
||||
OnSaveSettings(SettingsList);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Loads all settings.
|
||||
/// </summary>
|
||||
public void Load()
|
||||
{
|
||||
OnLoadSettings(SettingsList);
|
||||
}
|
||||
|
||||
private IEnumerable<Setting> BuildSettingsList()
|
||||
{
|
||||
// Iterate through all public instance properties
|
||||
foreach (PropertyInfo prop in GetType().GetProperties(BindingFlags.Instance | BindingFlags.Public))
|
||||
{
|
||||
// Ignore properties with ExcludedSetting attribute
|
||||
if (!Attribute.IsDefined(prop, typeof(ExcludedSettingAttribute)))
|
||||
{
|
||||
// Test for supported data type (same types as for Encryption class)
|
||||
if (!Encryption.IsTypeSupported(prop.PropertyType))
|
||||
throw new Exception(string.Format("Settings property '{0}' is an unsupported data type '{1}'. Change property type or use ExcludedSetting attribute.",
|
||||
prop.Name, prop.PropertyType.ToString()));
|
||||
bool encrypted = Attribute.IsDefined(prop, typeof(EncryptedSettingAttribute));
|
||||
if (encrypted && Encryption == null)
|
||||
throw new InvalidOperationException("Encryption password cannot be null if any settings have the EncryptedSetting attribute.");
|
||||
yield return new Setting(this, prop, encrypted);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
155
FSI.Lib/WinSettings/XmlSettings.cs
Normal file
155
FSI.Lib/WinSettings/XmlSettings.cs
Normal file
@@ -0,0 +1,155 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Xml;
|
||||
|
||||
namespace FSI.Lib.WinSettings
|
||||
{
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="XmlSettings"/> class makes it very easy to save your application
|
||||
/// settings to an XML file.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>
|
||||
/// To use the class, simply derive your own settings class from
|
||||
/// <see cref="XmlSettings" /> and add the public properties that you want to be
|
||||
/// saved as settings. You can then call the <see cref="Settings.Load"/> and
|
||||
/// <see cref="Settings.Save"/> methods to read or write those settings to an XML
|
||||
/// file.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// Your derived class' constructor should initialize your settings properties to
|
||||
/// their default values.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// Two attributes are available for public properties in your derived class. The
|
||||
/// first is <see cref="EncryptedSettingAttribute" />. Use this attribute if you
|
||||
/// want the setting to be encrypted when saved to file. When using this attribute on
|
||||
/// any property, you must provide a valid encryption password to the
|
||||
/// <see cref="XmlSettings" /> constructor.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// The second is the <see cref="ExcludedSettingAttribute"/>. Use this attribute
|
||||
/// on any properties that are used internally by your code and should not saved to
|
||||
/// file.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// All public properties without the <see cref="ExcludedSettingAttribute"></see>
|
||||
/// attribute must be of one of the supported data types. This includes all the basic
|
||||
/// data types as well as <see cref="string[]"></see> and <see cref="byte[]"></see>.
|
||||
/// All other types will raise an exception.
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
/// <example>
|
||||
/// The following example creates a settings class called <c>MySettings</c> with
|
||||
/// several properties, two of which are encrypted when saved to file.
|
||||
/// <code>
|
||||
/// public class MySettings : XmlSettings
|
||||
/// {
|
||||
/// // Define properties to be saved to file
|
||||
/// public string EmailHost { get; set; }
|
||||
/// public int EmailPort { get; set; }
|
||||
///
|
||||
/// // The following properties will be encrypted
|
||||
/// [EncryptedSetting]
|
||||
/// public string UserName { get; set; }
|
||||
/// [EncryptedSetting]
|
||||
/// public string Password { get; set; }
|
||||
///
|
||||
/// // The following property will not be saved to file
|
||||
/// // Non-public properties are also not saved to file
|
||||
/// [ExcludedSetting]
|
||||
/// public DateTime Created { get; set; }
|
||||
///
|
||||
/// public MySettings(string filename)
|
||||
/// : base(filename, "Password123")
|
||||
/// {
|
||||
/// // Set initial, default property values
|
||||
/// EmailHost = string.Empty;
|
||||
/// EmailPort = 0;
|
||||
/// UserName = string.Empty;
|
||||
/// Password = string.Empty;
|
||||
///
|
||||
/// Created = DateTime.Now;
|
||||
/// }
|
||||
/// }
|
||||
/// </code>
|
||||
/// </example>
|
||||
/// <seealso cref="IniSettings"/>
|
||||
/// <seealso cref="RegistrySettings"/>
|
||||
public abstract class XmlSettings : Settings
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets the name of the XML settings file.
|
||||
/// </summary>
|
||||
[ExcludedSetting]
|
||||
public string FileName { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Constructs an instance of the <see cref="XmlSettings"></see> class.
|
||||
/// </summary>
|
||||
/// <param name="filename">Name of the settings file.</param>
|
||||
/// <param name="password">Encryption password. May be <c>null</c> if
|
||||
/// no settings use the <see cref="EncryptedSettingAttribute" />
|
||||
/// attribute.</param>
|
||||
public XmlSettings(string filename, string password = null)
|
||||
: base(password)
|
||||
{
|
||||
|
||||
|
||||
if (string.IsNullOrWhiteSpace(filename))
|
||||
throw new ArgumentException("A valid file name is required.", nameof(filename));
|
||||
FileName = filename;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Performs internal load operations.
|
||||
/// </summary>
|
||||
/// <param name="settings">Settings to be loaded.</param>
|
||||
public override void OnLoadSettings(IEnumerable<Setting> settings)
|
||||
{
|
||||
if (File.Exists(FileName))
|
||||
{
|
||||
// Load XML document
|
||||
XmlDocument doc = new XmlDocument();
|
||||
doc.Load(FileName);
|
||||
// Read settings
|
||||
foreach (Setting setting in settings)
|
||||
{
|
||||
XmlNode node = doc.DocumentElement?.SelectSingleNode(setting.Name);
|
||||
if (node != null)
|
||||
setting.SetValueFromString(node.InnerText);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Performs internal save operations.
|
||||
/// </summary>
|
||||
/// <param name="settings">Settings to be saved.</param>
|
||||
public override void OnSaveSettings(IEnumerable<Setting> settings)
|
||||
{
|
||||
// Create settings document
|
||||
XmlDocument doc = new XmlDocument();
|
||||
doc.AppendChild(doc.CreateXmlDeclaration("1.0", "UTF-8", null));
|
||||
doc.AppendChild(doc.CreateElement("Settings"));
|
||||
// Write settings
|
||||
foreach (Setting setting in settings)
|
||||
{
|
||||
string value = setting.GetValueAsString();
|
||||
if (value != null)
|
||||
{
|
||||
XmlElement element = doc.CreateElement(setting.Name);
|
||||
element.InnerText = value;
|
||||
#if NET472
|
||||
doc.DocumentElement.AppendChild(element);
|
||||
#elif NET6_0
|
||||
doc.DocumentElement!.AppendChild(element);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
doc.Save(FileName);
|
||||
}
|
||||
}
|
||||
}
|
||||
27
FSI.Lib/Wpf/Converter/BooleanConverter.cs
Normal file
27
FSI.Lib/Wpf/Converter/BooleanConverter.cs
Normal file
@@ -0,0 +1,27 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Text;
|
||||
using System.Windows.Data;
|
||||
|
||||
namespace FSI.Lib.Wpf.Converter
|
||||
{
|
||||
public class BooleanConverter : IValueConverter
|
||||
{
|
||||
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
|
||||
{
|
||||
return !((bool)value);
|
||||
}
|
||||
|
||||
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
|
||||
{
|
||||
return value;
|
||||
}
|
||||
|
||||
public object ProvideValue(IServiceProvider serviceProvider)
|
||||
{
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
22
FSI.Lib/Wpf/Converters/InvertedBoolenConverter .cs
Normal file
22
FSI.Lib/Wpf/Converters/InvertedBoolenConverter .cs
Normal file
@@ -0,0 +1,22 @@
|
||||
using System;
|
||||
using System.Globalization;
|
||||
using System.Windows.Data;
|
||||
|
||||
namespace FSI.Lib.Wpf.Converters
|
||||
{
|
||||
|
||||
public class InvertedBoolenConverter : IValueConverter
|
||||
{
|
||||
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
|
||||
{
|
||||
return !(bool)value;
|
||||
|
||||
}
|
||||
|
||||
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
|
||||
{
|
||||
return (bool)value;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
16
FSI.Lib/Wpf/Ctrls/ChbWindowsTopMost.xaml
Normal file
16
FSI.Lib/Wpf/Ctrls/ChbWindowsTopMost.xaml
Normal file
@@ -0,0 +1,16 @@
|
||||
<UserControl x:Class="FSI.Lib.Wpf.Ctrls.ChbWindowsTopMost"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:local="clr-namespace:FSI.Lib.Wpf.Ctrls"
|
||||
mc:Ignorable="d"
|
||||
Width="Auto"
|
||||
Height="Auto">
|
||||
<StackPanel>
|
||||
<CheckBox Name="chbTopMost"
|
||||
Content="Fenster immer im Vordergrund"
|
||||
Checked="CheckBox_Checked"
|
||||
Unchecked="CheckBox_Unchecked" />
|
||||
</StackPanel>
|
||||
</UserControl>
|
||||
89
FSI.Lib/Wpf/Ctrls/ChbWindowsTopMost.xaml.cs
Normal file
89
FSI.Lib/Wpf/Ctrls/ChbWindowsTopMost.xaml.cs
Normal file
@@ -0,0 +1,89 @@
|
||||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Interop;
|
||||
|
||||
namespace FSI.Lib.Wpf.Ctrls
|
||||
{
|
||||
/// <summary>
|
||||
/// Interaktionslogik für ChbWindowsTopMost.xaml
|
||||
/// </summary>
|
||||
public partial class ChbWindowsTopMost : UserControl
|
||||
{
|
||||
private Window _window;
|
||||
|
||||
//A window receives this message when the user chooses a command from the Window menu, or when the user chooses the maximize button, minimize button, restore button, or close button.
|
||||
public const Int32 WM_SYSCOMMAND = 0x112;
|
||||
|
||||
//Draws a horizontal dividing line.This flag is used only in a drop-down menu, submenu, or shortcut menu.The line cannot be grayed, disabled, or highlighted.
|
||||
public const Int32 MF_SEPARATOR = 0x800;
|
||||
|
||||
//Specifies that an ID is a position index into the menu and not a command ID.
|
||||
public const Int32 MF_BYPOSITION = 0x400;
|
||||
|
||||
//Specifies that the menu item is a text string.
|
||||
public const Int32 MF_STRING = 0x0;
|
||||
|
||||
//Menu Ids for our custom menu items
|
||||
public const Int32 _ItemTopMostId = 1000;
|
||||
|
||||
[DllImport("user32.dll")]
|
||||
private static extern IntPtr GetSystemMenu(IntPtr hWnd, bool bRevert);
|
||||
|
||||
[DllImport("user32.dll")]
|
||||
private static extern bool InsertMenu(IntPtr hMenu, Int32 wPosition, Int32 wFlags, Int32 wIDNewItem, string lpNewItem);
|
||||
|
||||
public ChbWindowsTopMost()
|
||||
{
|
||||
InitializeComponent();
|
||||
Loaded += ChbWindowsTopMost_Loaded;
|
||||
}
|
||||
|
||||
private void ChbWindowsTopMost_Loaded(object sender, RoutedEventArgs e)
|
||||
{
|
||||
_window = Window.GetWindow(this);
|
||||
|
||||
IntPtr windowhandle = new WindowInteropHelper(_window).Handle;
|
||||
HwndSource hwndSource = HwndSource.FromHwnd(windowhandle);
|
||||
|
||||
//Get the handle for the system menu
|
||||
IntPtr systemMenuHandle = GetSystemMenu(windowhandle, false);
|
||||
|
||||
//Insert our custom menu items
|
||||
InsertMenu(systemMenuHandle, 5, MF_BYPOSITION | MF_SEPARATOR, 0, string.Empty); //Add a menu seperator
|
||||
InsertMenu(systemMenuHandle, 6, MF_BYPOSITION, _ItemTopMostId, "immer im Vordergrund"); //Add a setting menu item
|
||||
|
||||
hwndSource.AddHook(new HwndSourceHook(WndProc));
|
||||
}
|
||||
private IntPtr WndProc(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled)
|
||||
{
|
||||
// Check if the SystemCommand message has been executed
|
||||
if (msg == WM_SYSCOMMAND)
|
||||
{
|
||||
//check which menu item was clicked
|
||||
switch (wParam.ToInt32())
|
||||
{
|
||||
case _ItemTopMostId:
|
||||
_window.Topmost = !_window.Topmost;
|
||||
chbTopMost.IsChecked = _window.Topmost;
|
||||
handled = true;
|
||||
break;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
return IntPtr.Zero;
|
||||
}
|
||||
|
||||
private void CheckBox_Checked(object sender, RoutedEventArgs e)
|
||||
{
|
||||
_window.Topmost = true;
|
||||
}
|
||||
|
||||
private void CheckBox_Unchecked(object sender, RoutedEventArgs e)
|
||||
{
|
||||
_window.Topmost = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
78
FSI.Lib/Wpf/Ctrls/FilterDataGrid/DataGridColumn.cs
Normal file
78
FSI.Lib/Wpf/Ctrls/FilterDataGrid/DataGridColumn.cs
Normal file
@@ -0,0 +1,78 @@
|
||||
using System.Windows;
|
||||
|
||||
namespace FSI.Lib.Wpf.Ctrls.FilterDataGrid
|
||||
{
|
||||
public sealed class DataGridTemplateColumn : System.Windows.Controls.DataGridTemplateColumn
|
||||
{
|
||||
#region Public Fields
|
||||
|
||||
/// <summary>
|
||||
/// FieldName Dependency Property.
|
||||
/// </summary>
|
||||
public static readonly DependencyProperty FieldNameProperty =
|
||||
DependencyProperty.Register("FieldName", typeof(string), typeof(DataGridTemplateColumn),
|
||||
new PropertyMetadata(""));
|
||||
|
||||
/// <summary>
|
||||
/// IsColumnFiltered Dependency Property.
|
||||
/// </summary>
|
||||
public static readonly DependencyProperty IsColumnFilteredProperty =
|
||||
DependencyProperty.Register("IsColumnFiltered", typeof(bool), typeof(DataGridTemplateColumn),
|
||||
new PropertyMetadata(false));
|
||||
|
||||
#endregion Public Fields
|
||||
|
||||
#region Public Properties
|
||||
|
||||
public string FieldName
|
||||
{
|
||||
get => (string)GetValue(FieldNameProperty);
|
||||
set => SetValue(FieldNameProperty, value);
|
||||
}
|
||||
|
||||
public bool IsColumnFiltered
|
||||
{
|
||||
get => (bool)GetValue(IsColumnFilteredProperty);
|
||||
set => SetValue(IsColumnFilteredProperty, value);
|
||||
}
|
||||
|
||||
#endregion Public Properties
|
||||
}
|
||||
|
||||
public sealed class DataGridTextColumn : System.Windows.Controls.DataGridTextColumn
|
||||
{
|
||||
#region Public Fields
|
||||
|
||||
/// <summary>
|
||||
/// FieldName Dependency Property.
|
||||
/// </summary>
|
||||
public static readonly DependencyProperty FieldNameProperty =
|
||||
DependencyProperty.Register("FieldName", typeof(string), typeof(DataGridTextColumn),
|
||||
new PropertyMetadata(""));
|
||||
|
||||
/// <summary>
|
||||
/// IsColumnFiltered Dependency Property.
|
||||
/// </summary>
|
||||
public static readonly DependencyProperty IsColumnFilteredProperty =
|
||||
DependencyProperty.Register("IsColumnFiltered", typeof(bool), typeof(DataGridTextColumn),
|
||||
new PropertyMetadata(false));
|
||||
|
||||
#endregion Public Fields
|
||||
|
||||
#region Public Properties
|
||||
|
||||
public string FieldName
|
||||
{
|
||||
get => (string)GetValue(FieldNameProperty);
|
||||
set => SetValue(FieldNameProperty, value);
|
||||
}
|
||||
|
||||
public bool IsColumnFiltered
|
||||
{
|
||||
get => (bool)GetValue(IsColumnFilteredProperty);
|
||||
set => SetValue(IsColumnFilteredProperty, value);
|
||||
}
|
||||
|
||||
#endregion Public Properties
|
||||
}
|
||||
}
|
||||
319
FSI.Lib/Wpf/Ctrls/FilterDataGrid/FilterCommon.cs
Normal file
319
FSI.Lib/Wpf/Ctrls/FilterDataGrid/FilterCommon.cs
Normal file
@@ -0,0 +1,319 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace FSI.Lib.Wpf.Ctrls.FilterDataGrid
|
||||
{
|
||||
public sealed class FilterCommon : NotifyProperty
|
||||
{
|
||||
#region Public Constructors
|
||||
|
||||
public FilterCommon()
|
||||
{
|
||||
PreviouslyFilteredItems = new HashSet<object>(EqualityComparer<object>.Default);
|
||||
}
|
||||
|
||||
#endregion Public Constructors
|
||||
|
||||
#region Public Properties
|
||||
|
||||
public string FieldName { get; set; }
|
||||
public Type FieldType { get; set; }
|
||||
public bool IsFiltered { get; set; }
|
||||
public HashSet<object> PreviouslyFilteredItems { get; set; }
|
||||
|
||||
// Treeview
|
||||
public List<FilterItem> Tree { get; set; }
|
||||
|
||||
public Loc Translate { get; set; }
|
||||
|
||||
#endregion Public Properties
|
||||
|
||||
#region Private Methods
|
||||
|
||||
/// <summary>
|
||||
/// Recursive call for check/uncheck all items in tree
|
||||
/// </summary>
|
||||
/// <param name="state"></param>
|
||||
/// <param name="updateChildren"></param>
|
||||
/// <param name="updateParent"></param>
|
||||
private void SetIsChecked(FilterItem item, bool? state, bool updateChildren, bool updateParent)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (state == item.IsChecked) return;
|
||||
item.SetState = state;
|
||||
|
||||
// select all / unselect all
|
||||
if (item.Level == 0)
|
||||
Tree.Where(t => t.Level != 0).ToList().ForEach(c => { SetIsChecked(c, state, true, true); });
|
||||
|
||||
// update children
|
||||
if (updateChildren && item.IsChecked.HasValue)
|
||||
item.Children?.ForEach(c => { SetIsChecked(c, state, true, false); });
|
||||
|
||||
// update parent
|
||||
if (updateParent && item.Parent != null)
|
||||
VerifyCheckedState(item.Parent);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Debug.WriteLine($"FilterCommon.SetState : {ex.Message}");
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Update the tree when the state of the IsChecked property changes
|
||||
/// </summary>
|
||||
/// <param name="o">item</param>
|
||||
/// <param name="e">state</param>
|
||||
public void UpdateTree(object o, bool? e)
|
||||
{
|
||||
if (o == null) return;
|
||||
var item = (FilterItem)o;
|
||||
SetIsChecked(item, e, true, true);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Check or uncheck parents or children
|
||||
/// </summary>
|
||||
private void VerifyCheckedState(FilterItem item)
|
||||
{
|
||||
bool? state = null;
|
||||
|
||||
for (var i = 0; i < item.Children?.Count; ++i)
|
||||
{
|
||||
var current = item.Children[i].IsChecked;
|
||||
|
||||
if (i == 0)
|
||||
{
|
||||
state = current;
|
||||
}
|
||||
else if (state != current)
|
||||
{
|
||||
state = null;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
SetIsChecked(item, state, false, true);
|
||||
}
|
||||
|
||||
#endregion Private Methods
|
||||
|
||||
#region Public Methods
|
||||
|
||||
/// <summary>
|
||||
/// Add the filter to the predicate dictionary
|
||||
/// </summary>
|
||||
public void AddFilter(Dictionary<string, Predicate<object>> criteria)
|
||||
{
|
||||
if (IsFiltered) return;
|
||||
|
||||
// predicate of filter
|
||||
bool Predicate(object o)
|
||||
{
|
||||
var value = o.GetType().GetProperty(FieldName)?.GetValue(o, null);
|
||||
return !PreviouslyFilteredItems.Contains(value);
|
||||
}
|
||||
|
||||
// add to list of predicates
|
||||
criteria.Add(FieldName, Predicate);
|
||||
|
||||
IsFiltered = true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Any Date IsChecked, check if any tree item is checked (can apply filter)
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public bool AnyDateIsChecked()
|
||||
{
|
||||
// any IsChecked is true or null
|
||||
// IsDate Checked has three states, isChecked: null and true
|
||||
return Tree != null && Tree.Skip(1).Any(t => t.IsChecked != false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Any state of Date Changed, check if at least one date is checked and another is changed
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public bool AnyDateChanged()
|
||||
{
|
||||
// any (year, month, day) status changed
|
||||
return Tree != null &&
|
||||
Tree.Skip(1)
|
||||
.Any(year => year.Changed || year.Children
|
||||
.Any(month => month.Changed || month.Children
|
||||
.Any(day => day.Changed))) && AnyDateIsChecked();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Build the item tree
|
||||
/// </summary>
|
||||
/// <param name="dates"></param>
|
||||
/// <param name="currentFilter"></param>
|
||||
/// <param name="uncheckPrevious"></param>
|
||||
/// <returns></returns>
|
||||
public IEnumerable<FilterItem> BuildTree(IEnumerable<object> dates, string lastFilter = null)
|
||||
{
|
||||
|
||||
try
|
||||
{
|
||||
var uncheckPrevious = FieldName == lastFilter;
|
||||
var type = typeof(DateTime);
|
||||
|
||||
Tree = new List<FilterItem>
|
||||
{
|
||||
new FilterItem(this)
|
||||
{
|
||||
Label = Translate.All, CurrentFilter = this, Content = 0, Level = 0, SetState = true, FieldType = type
|
||||
}
|
||||
};
|
||||
|
||||
if (dates == null) return Tree;
|
||||
// iterate over all items that are not null
|
||||
// INFO:
|
||||
// SetState : does not raise OnDateStatusChanged event
|
||||
// IsChecked : raise OnDateStatusChanged event
|
||||
// (see the FilterItem class for more informations)
|
||||
|
||||
var dateTimes = dates.ToList();
|
||||
|
||||
foreach (var y in from date in dateTimes.Where(d => d != null)
|
||||
.Select(d => (DateTime)d).OrderBy(o => o.Year)
|
||||
group date by date.Year into year
|
||||
select new FilterItem(this)
|
||||
{
|
||||
// YEAR
|
||||
Level = 1,
|
||||
CurrentFilter = this,
|
||||
Content = year.Key,
|
||||
Label = year.First().ToString("yyyy", Translate.Culture),
|
||||
SetState = true, // default state
|
||||
FieldType = type,
|
||||
|
||||
Children = (from date in year
|
||||
group date by date.Month into month
|
||||
select new FilterItem(this)
|
||||
{
|
||||
// MOUNTH
|
||||
Level = 2,
|
||||
CurrentFilter = this,
|
||||
Content = month.Key,
|
||||
Label = month.First().ToString("MMMM", Translate.Culture),
|
||||
SetState = true, // default state
|
||||
FieldType = type,
|
||||
|
||||
Children = (from day in month
|
||||
select new FilterItem(this)
|
||||
{
|
||||
// DAY
|
||||
Level = 3,
|
||||
CurrentFilter = this,
|
||||
Content = day.Day,
|
||||
Label = day.ToString("dd", Translate.Culture),
|
||||
SetState = true, // default state
|
||||
FieldType = type,
|
||||
Children = new List<FilterItem>()
|
||||
}).ToList()
|
||||
}).ToList()
|
||||
})
|
||||
{
|
||||
// set parent and IsChecked property if uncheckPrevious items
|
||||
y.Children.ForEach(m =>
|
||||
{
|
||||
m.Parent = y;
|
||||
|
||||
m.Children.ForEach(d =>
|
||||
{
|
||||
d.Parent = m;
|
||||
|
||||
// set the state of the ischecked property based on the items already filtered (unchecked)
|
||||
if (PreviouslyFilteredItems != null && uncheckPrevious)
|
||||
d.IsChecked = PreviouslyFilteredItems
|
||||
.Any(u => u != null && u.Equals(new DateTime((int)y.Content, (int)m.Content, (int)d.Content))) == false;
|
||||
|
||||
// reset initialization with new state
|
||||
d.InitialState = d.IsChecked;
|
||||
});
|
||||
|
||||
// reset initialization with new state
|
||||
m.InitialState = m.IsChecked;
|
||||
});
|
||||
|
||||
// reset initialization with new state
|
||||
y.InitialState = y.IsChecked;
|
||||
|
||||
Tree.Add(y);
|
||||
}
|
||||
|
||||
// last empty item if exist in collection
|
||||
if (dateTimes.Any(d => d == null))
|
||||
Tree.Add(
|
||||
new FilterItem(this)
|
||||
{
|
||||
Label = Translate.Empty, // translation
|
||||
CurrentFilter = this,
|
||||
Content = null,
|
||||
Level = -1,
|
||||
FieldType = type,
|
||||
SetState = PreviouslyFilteredItems?.Any(u => u == null) == false,
|
||||
Children = new List<FilterItem>()
|
||||
}
|
||||
);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Debug.WriteLine($"FilterCommon.BuildTree : {ex.Message}");
|
||||
throw;
|
||||
}
|
||||
|
||||
return Tree;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get all the items from the tree (checked or unchecked)
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public IEnumerable<FilterItem> GetAllItemsTree()
|
||||
{
|
||||
var filterCommon = new List<FilterItem>();
|
||||
|
||||
try
|
||||
{
|
||||
// skip first item (select all)
|
||||
foreach (var y in Tree.Skip(1))
|
||||
if (y.Level > 0) // year :1, mounth : 2, day : 3
|
||||
filterCommon.AddRange(
|
||||
from m in y.Children
|
||||
from d in m.Children
|
||||
select new FilterItem
|
||||
{
|
||||
Content = new DateTime((int)y.Content, (int)m.Content, (int)d.Content),
|
||||
IsChecked = d.IsChecked ?? false,
|
||||
});
|
||||
else // null date (Level -1)
|
||||
filterCommon.Add(new FilterItem
|
||||
{
|
||||
Content = null,
|
||||
IsChecked = y.IsChecked ?? false,
|
||||
});
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Debug.WriteLine($"FilterCommon.GetAllItemsTree : {ex.Message}");
|
||||
throw;
|
||||
}
|
||||
|
||||
return filterCommon;
|
||||
}
|
||||
|
||||
#endregion Public Methods
|
||||
}
|
||||
}
|
||||
|
||||
1309
FSI.Lib/Wpf/Ctrls/FilterDataGrid/FilterDataGrid.cs
Normal file
1309
FSI.Lib/Wpf/Ctrls/FilterDataGrid/FilterDataGrid.cs
Normal file
File diff suppressed because it is too large
Load Diff
20
FSI.Lib/Wpf/Ctrls/FilterDataGrid/FilterDataGridDictionary.cs
Normal file
20
FSI.Lib/Wpf/Ctrls/FilterDataGrid/FilterDataGridDictionary.cs
Normal file
@@ -0,0 +1,20 @@
|
||||
namespace FSI.Lib.Wpf.Ctrls.FilterDataGrid
|
||||
{
|
||||
/// <summary>
|
||||
/// ResourceDictionary
|
||||
/// </summary>
|
||||
public partial class FilterDataGridDictionary
|
||||
{
|
||||
#region Public Constructors
|
||||
|
||||
/// <summary>
|
||||
/// FilterDataGrid Dictionary
|
||||
/// </summary>
|
||||
public FilterDataGridDictionary()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
|
||||
#endregion Public Constructors
|
||||
}
|
||||
}
|
||||
396
FSI.Lib/Wpf/Ctrls/FilterDataGrid/FilterHelpers.cs
Normal file
396
FSI.Lib/Wpf/Ctrls/FilterDataGrid/FilterHelpers.cs
Normal file
@@ -0,0 +1,396 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.Diagnostics;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Controls.Primitives;
|
||||
using System.Windows.Media;
|
||||
|
||||
namespace FSI.Lib.Wpf.Ctrls.FilterDataGrid
|
||||
{
|
||||
public static class Extensions
|
||||
{
|
||||
#region Public Methods
|
||||
|
||||
public static HashSet<T> ToHashSet<T>(this IEnumerable<T> source, IEqualityComparer<T> comparer = null)
|
||||
{
|
||||
return new HashSet<T>(source, comparer);
|
||||
}
|
||||
|
||||
#endregion Public Methods
|
||||
}
|
||||
|
||||
public static class Helpers
|
||||
{
|
||||
#region Public Methods
|
||||
|
||||
/// <summary>
|
||||
/// Print elapsed time
|
||||
/// </summary>
|
||||
/// <param name="label"></param>
|
||||
/// <param name="start"></param>
|
||||
public static void Elapsed(string label, DateTime start)
|
||||
{
|
||||
var span = DateTime.Now - start;
|
||||
Debug.WriteLine($"{label,-20}{span:mm\\:ss\\.ff}");
|
||||
}
|
||||
|
||||
#endregion Public Methods
|
||||
}
|
||||
|
||||
public static class VisualTreeHelpers
|
||||
{
|
||||
#region Private Methods
|
||||
|
||||
private static T FindVisualChild<T>(this DependencyObject dependencyObject, string name)
|
||||
where T : DependencyObject
|
||||
{
|
||||
// Search immediate children first (breadth-first)
|
||||
var childrenCount = VisualTreeHelper.GetChildrenCount(dependencyObject);
|
||||
|
||||
//http://stackoverflow.com/questions/12304904/why-visualtreehelper-getchildrencount-returns-0-for-popup
|
||||
|
||||
if (childrenCount == 0 && dependencyObject is Popup)
|
||||
{
|
||||
var popup = dependencyObject as Popup;
|
||||
return popup.Child?.FindVisualChild<T>(name);
|
||||
}
|
||||
|
||||
for (var i = 0; i < childrenCount; i++)
|
||||
{
|
||||
var child = VisualTreeHelper.GetChild(dependencyObject, i);
|
||||
var nameOfChild = child.GetValue(FrameworkElement.NameProperty) as string;
|
||||
|
||||
if (child is T && (name == string.Empty || name == nameOfChild))
|
||||
return (T)child;
|
||||
var childOfChild = child.FindVisualChild<T>(name);
|
||||
if (childOfChild != null)
|
||||
return childOfChild;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private static IEnumerable<T> GetChildrenOf<T>(this DependencyObject obj, bool recursive) where T : DependencyObject
|
||||
{
|
||||
var count = VisualTreeHelper.GetChildrenCount(obj);
|
||||
for (var i = 0; i < count; i++)
|
||||
{
|
||||
var child = VisualTreeHelper.GetChild(obj, i);
|
||||
if (child is T) yield return (T)child;
|
||||
|
||||
if (recursive)
|
||||
foreach (var item in child.GetChildrenOf<T>())
|
||||
yield return item;
|
||||
}
|
||||
}
|
||||
|
||||
private static IEnumerable<T> GetChildrenOf<T>(this DependencyObject obj) where T : DependencyObject
|
||||
{
|
||||
return obj.GetChildrenOf<T>(false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This method is an alternative to WPF's
|
||||
/// <see cref="VisualTreeHelper.GetParent" /> method, which also
|
||||
/// supports content elements. Keep in mind that for content element,
|
||||
/// this method falls back to the logical tree of the element!
|
||||
/// </summary>
|
||||
/// <param name="child">The item to be processed.</param>
|
||||
/// <returns>
|
||||
/// The submitted item's parent, if available. Otherwise
|
||||
/// null.
|
||||
/// </returns>
|
||||
private static DependencyObject GetParentObject(this DependencyObject child)
|
||||
{
|
||||
if (child == null) return null;
|
||||
|
||||
//handle content elements separately
|
||||
var contentElement = child as ContentElement;
|
||||
if (contentElement != null)
|
||||
{
|
||||
var parent = ContentOperations.GetParent(contentElement);
|
||||
if (parent != null) return parent;
|
||||
|
||||
var fce = contentElement as FrameworkContentElement;
|
||||
return fce?.Parent;
|
||||
}
|
||||
|
||||
//also try searching for parent in framework elements (such as DockPanel, etc)
|
||||
var frameworkElement = child as FrameworkElement;
|
||||
if (frameworkElement != null)
|
||||
{
|
||||
var parent = frameworkElement.Parent;
|
||||
if (parent != null) return parent;
|
||||
}
|
||||
|
||||
//if it's not a ContentElement/FrameworkElement, rely on VisualTreeHelper
|
||||
return VisualTreeHelper.GetParent(child);
|
||||
}
|
||||
#endregion Private Methods
|
||||
|
||||
#region Public Methods
|
||||
|
||||
/// <summary>
|
||||
/// Returns the first ancester of specified type
|
||||
/// </summary>
|
||||
public static T FindAncestor<T>(DependencyObject current)
|
||||
where T : DependencyObject
|
||||
{
|
||||
current = VisualTreeHelper.GetParent(current);
|
||||
|
||||
while (current != null)
|
||||
{
|
||||
if (current is T) return (T)current;
|
||||
|
||||
current = VisualTreeHelper.GetParent(current);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns a specific ancester of an object
|
||||
/// </summary>
|
||||
public static T FindAncestor<T>(DependencyObject current, T lookupItem)
|
||||
where T : DependencyObject
|
||||
{
|
||||
while (current != null)
|
||||
{
|
||||
if (current is T && current == lookupItem) return (T)current;
|
||||
|
||||
current = VisualTreeHelper.GetParent(current);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Finds an ancestor object by name and type
|
||||
/// </summary>
|
||||
public static T FindAncestor<T>(DependencyObject current, string parentName)
|
||||
where T : DependencyObject
|
||||
{
|
||||
while (current != null)
|
||||
{
|
||||
if (!string.IsNullOrEmpty(parentName))
|
||||
{
|
||||
var frameworkElement = current as FrameworkElement;
|
||||
if (current is T && frameworkElement != null && frameworkElement.Name == parentName)
|
||||
return (T)current;
|
||||
}
|
||||
else if (current is T)
|
||||
{
|
||||
return (T)current;
|
||||
}
|
||||
|
||||
current = VisualTreeHelper.GetParent(current);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks for a child control within a parent by name
|
||||
/// </summary>
|
||||
public static T FindChild<T>(DependencyObject parent, string childName) where T : DependencyObject
|
||||
{
|
||||
// Confirm parent and childName are valid.
|
||||
if (parent == null) return null;
|
||||
|
||||
T foundChild = null;
|
||||
|
||||
var childrenCount = VisualTreeHelper.GetChildrenCount(parent);
|
||||
for (var i = 0; i < childrenCount; i++)
|
||||
{
|
||||
var child = VisualTreeHelper.GetChild(parent, i);
|
||||
// If the child is not of the request child type child
|
||||
var childType = child as T;
|
||||
if (childType == null)
|
||||
{
|
||||
// recursively drill down the tree
|
||||
foundChild = FindChild<T>(child, childName);
|
||||
|
||||
// If the child is found, break so we do not overwrite the found child.
|
||||
if (foundChild != null) break;
|
||||
}
|
||||
else if (!string.IsNullOrEmpty(childName))
|
||||
{
|
||||
var frameworkElement = child as FrameworkElement;
|
||||
// If the child's name is set for search
|
||||
if (frameworkElement != null && frameworkElement.Name == childName)
|
||||
{
|
||||
// if the child's name is of the request name
|
||||
foundChild = (T)child;
|
||||
break;
|
||||
}
|
||||
|
||||
// recursively drill down the tree
|
||||
foundChild = FindChild<T>(child, childName);
|
||||
|
||||
// If the child is found, break so we do not overwrite the found child.
|
||||
if (foundChild != null) break;
|
||||
}
|
||||
else
|
||||
{
|
||||
// child element found.
|
||||
foundChild = (T)child;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return foundChild;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks for a child control within a parent by type
|
||||
/// </summary>
|
||||
public static T FindChild<T>(DependencyObject parent)
|
||||
where T : DependencyObject
|
||||
{
|
||||
// Confirm parent is valid.
|
||||
if (parent == null) return null;
|
||||
|
||||
T foundChild = null;
|
||||
|
||||
var childrenCount = VisualTreeHelper.GetChildrenCount(parent);
|
||||
for (var i = 0; i < childrenCount; i++)
|
||||
{
|
||||
var child = VisualTreeHelper.GetChild(parent, i);
|
||||
// If the child is not of the request child type child
|
||||
var childType = child as T;
|
||||
if (childType == null)
|
||||
{
|
||||
// recursively drill down the tree
|
||||
foundChild = FindChild<T>(child);
|
||||
|
||||
// If the child is found, break so we do not overwrite the found child.
|
||||
if (foundChild != null) break;
|
||||
}
|
||||
else
|
||||
{
|
||||
// child element found.
|
||||
foundChild = (T)child;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return foundChild;
|
||||
}
|
||||
|
||||
public static T FindVisualChild<T>(this DependencyObject dependencyObject) where T : DependencyObject
|
||||
{
|
||||
return dependencyObject.FindVisualChild<T>(string.Empty);
|
||||
}
|
||||
|
||||
public static Visual GetDescendantByType(Visual element, Type type)
|
||||
{
|
||||
if (element == null) return null;
|
||||
if (element.GetType() == type) return element;
|
||||
Visual foundElement = null;
|
||||
if (element is FrameworkElement frameworkElement) frameworkElement.ApplyTemplate();
|
||||
for (var i = 0; i < VisualTreeHelper.GetChildrenCount(element); i++)
|
||||
{
|
||||
var visual = VisualTreeHelper.GetChild(element, i) as Visual;
|
||||
foundElement = GetDescendantByType(visual, type);
|
||||
if (foundElement != null) break;
|
||||
}
|
||||
|
||||
return foundElement;
|
||||
}
|
||||
|
||||
public static DataGridColumnHeader GetHeader(DataGridColumn column, DependencyObject reference)
|
||||
{
|
||||
for (var i = 0; i < VisualTreeHelper.GetChildrenCount(reference); i++)
|
||||
{
|
||||
var child = VisualTreeHelper.GetChild(reference, i);
|
||||
|
||||
if (child is DataGridColumnHeader colHeader && colHeader.Column == column) return colHeader;
|
||||
|
||||
colHeader = GetHeader(column, child);
|
||||
if (colHeader != null) return colHeader;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Finds a parent of a given item on the visual tree.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of the queried item.</typeparam>
|
||||
/// <param name="child">
|
||||
/// A direct or indirect child of the
|
||||
/// queried item.
|
||||
/// </param>
|
||||
/// <returns>
|
||||
/// The first parent item that matches the submitted
|
||||
/// type parameter. If not matching item can be found, a null
|
||||
/// reference is being returned.
|
||||
/// </returns>
|
||||
public static T TryFindParent<T>(this DependencyObject child) where T : DependencyObject
|
||||
{
|
||||
//get parent item
|
||||
var parentObject = GetParentObject(child);
|
||||
|
||||
//we've reached the end of the tree
|
||||
if (parentObject == null) return null;
|
||||
|
||||
//check if the parent matches the type we're looking for
|
||||
var parent = parentObject as T;
|
||||
if (parent != null)
|
||||
return parent;
|
||||
return TryFindParent<T>(parentObject);
|
||||
}
|
||||
#endregion Public Methods
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Base class for all ViewModel classes in the application. Provides support for
|
||||
/// property changes notification.
|
||||
/// </summary>
|
||||
[Serializable]
|
||||
public abstract class NotifyProperty : INotifyPropertyChanged
|
||||
{
|
||||
#region Public Events
|
||||
|
||||
/// <summary>
|
||||
/// Raised when a property on this object has a new value.
|
||||
/// </summary>
|
||||
public event PropertyChangedEventHandler PropertyChanged;
|
||||
|
||||
#endregion Public Events
|
||||
|
||||
#region Private Methods
|
||||
|
||||
/// <summary>
|
||||
/// Warns the developer if this object does not have a public property with
|
||||
/// the specified name. This method does not exist in a Release build.
|
||||
/// </summary>
|
||||
[Conditional("DEBUG")]
|
||||
[DebuggerStepThrough]
|
||||
private void VerifyPropertyName(string propertyName)
|
||||
{
|
||||
// verify that the property name matches a real,
|
||||
// public, instance property on this object.
|
||||
if (TypeDescriptor.GetProperties(this)[propertyName] == null)
|
||||
Debug.Fail("Invalid property name: " + propertyName);
|
||||
}
|
||||
|
||||
#endregion Private Methods
|
||||
|
||||
#region Public Methods
|
||||
|
||||
/// <summary>
|
||||
/// Raises this object's PropertyChanged event.
|
||||
/// </summary>
|
||||
/// <param name="propertyName">The name of the property that has a new value.</param>
|
||||
public void OnPropertyChanged(string propertyName)
|
||||
{
|
||||
VerifyPropertyName(propertyName);
|
||||
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
|
||||
}
|
||||
|
||||
#endregion Public Methods
|
||||
}
|
||||
}
|
||||
147
FSI.Lib/Wpf/Ctrls/FilterDataGrid/FilterItem.cs
Normal file
147
FSI.Lib/Wpf/Ctrls/FilterDataGrid/FilterItem.cs
Normal file
@@ -0,0 +1,147 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace FSI.Lib.Wpf.Ctrls.FilterDataGrid
|
||||
{
|
||||
public class FilterItem : NotifyProperty
|
||||
{
|
||||
#region Public Events
|
||||
|
||||
private event EventHandler<bool?> OnDateStatusChanged;
|
||||
|
||||
#endregion Public Events
|
||||
|
||||
#region Constructor
|
||||
|
||||
public FilterItem(FilterCommon action = null)
|
||||
{
|
||||
// event subscription
|
||||
if (action != null)
|
||||
OnDateStatusChanged += action.UpdateTree;
|
||||
}
|
||||
|
||||
#endregion Constructor
|
||||
|
||||
#region Private Fields
|
||||
|
||||
private bool? isChecked;
|
||||
private bool initialized;
|
||||
|
||||
#endregion Private Fields
|
||||
|
||||
#region Public Properties
|
||||
|
||||
/// <summary>
|
||||
///Children higher levels (years, months)
|
||||
/// </summary>
|
||||
public List<FilterItem> Children { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Raw value of the item (not displayed, see Label property)
|
||||
/// </summary>
|
||||
public object Content { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Content length
|
||||
/// </summary>
|
||||
public int ContentLength { get; set; }
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Current filter
|
||||
/// </summary>
|
||||
public FilterCommon CurrentFilter { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Field type
|
||||
/// </summary>
|
||||
public Type FieldType { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Initial state
|
||||
/// </summary>
|
||||
public bool? InitialState { get; set; }
|
||||
|
||||
public int Id { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// State of checkbox
|
||||
/// </summary>
|
||||
public bool? IsChecked
|
||||
{
|
||||
get => isChecked;
|
||||
set
|
||||
{
|
||||
if (!initialized)
|
||||
{
|
||||
InitialState = value;
|
||||
initialized = true;
|
||||
isChecked = value; // don't remove
|
||||
|
||||
// the iteration over an Collection triggers the notification
|
||||
// of the "IsChecked" property and slows the performance of the loop,
|
||||
// the return prevents the OnPropertyChanged
|
||||
// notification at initialization
|
||||
return;
|
||||
}
|
||||
|
||||
// raise event to update the date tree, see FilterCommon class
|
||||
// only type date type fields are subscribed to the OnDateStatusChanged event
|
||||
// OnDateStatusChanged is not triggered at tree initialization
|
||||
if (FieldType == typeof(DateTime))
|
||||
{
|
||||
OnDateStatusChanged?.Invoke(this, value);
|
||||
}
|
||||
else
|
||||
{
|
||||
isChecked = value;
|
||||
OnPropertyChanged("IsChecked");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Content displayed
|
||||
/// </summary>
|
||||
public string Label { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Hierarchical level for the date
|
||||
/// </summary>
|
||||
public int Level { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Parent of lower levels (days, months)
|
||||
/// </summary>
|
||||
public FilterItem Parent { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Set the state of the IsChecked property for date, does not invoke the update of the tree
|
||||
/// </summary>
|
||||
public bool? SetState
|
||||
{
|
||||
get => isChecked;
|
||||
set
|
||||
{
|
||||
isChecked = value;
|
||||
|
||||
if (!initialized)
|
||||
{
|
||||
InitialState = value;
|
||||
initialized = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
OnPropertyChanged("IsChecked");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks if the initial state has changed
|
||||
/// </summary>
|
||||
public bool Changed => isChecked != InitialState;
|
||||
|
||||
#endregion Public Properties
|
||||
}
|
||||
}
|
||||
235
FSI.Lib/Wpf/Ctrls/FilterDataGrid/Loc.cs
Normal file
235
FSI.Lib/Wpf/Ctrls/FilterDataGrid/Loc.cs
Normal file
@@ -0,0 +1,235 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
|
||||
namespace FSI.Lib.Wpf.Ctrls.FilterDataGrid
|
||||
{
|
||||
public enum Local
|
||||
{
|
||||
Chinese,
|
||||
Dutch,
|
||||
English,
|
||||
French,
|
||||
German,
|
||||
Italian,
|
||||
Russian,
|
||||
}
|
||||
|
||||
public class Loc
|
||||
{
|
||||
#region Private Fields
|
||||
|
||||
private Local language;
|
||||
|
||||
// culture name(used for dates)
|
||||
private static readonly Dictionary<Local, string> CultureNames = new Dictionary<Local, string>
|
||||
{
|
||||
{ Local.Chinese, "zh-Hans" },
|
||||
{ Local.Dutch, "nl-NL" },
|
||||
{ Local.English, "en-US" },
|
||||
{ Local.French, "fr-FR" },
|
||||
{ Local.German, "de-DE" },
|
||||
{ Local.Italian, "it-IT" },
|
||||
{ Local.Russian, "ru-RU" },
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Translation dictionary
|
||||
/// </summary>
|
||||
private static readonly Dictionary<string, Dictionary<Local, string>> Translation =
|
||||
new Dictionary<string, Dictionary<Local, string>>
|
||||
{
|
||||
{
|
||||
"All", new Dictionary<Local, string>
|
||||
{
|
||||
{ Local.Chinese, "(全选)" },
|
||||
{ Local.Dutch, "(Alles selecteren)" },
|
||||
{ Local.English, "(Select all)" },
|
||||
{ Local.French, "(Sélectionner tout)" },
|
||||
{ Local.German, "(Alle auswählen)" },
|
||||
{ Local.Italian, "(Seleziona tutto)" },
|
||||
{ Local.Russian, "(Выбрать все)" },
|
||||
}
|
||||
},
|
||||
{
|
||||
"Empty", new Dictionary<Local, string>
|
||||
{
|
||||
{ Local.Chinese, "(空白)" },
|
||||
{ Local.Dutch, "(Leeg)" },
|
||||
{ Local.English, "(Blank)" },
|
||||
{ Local.French, "(Vides)" },
|
||||
{ Local.German, "(Leer)" },
|
||||
{ Local.Italian, "(Vuoto)" },
|
||||
{ Local.Russian, "(Заготовки)" },
|
||||
}
|
||||
},
|
||||
{
|
||||
"Clear", new Dictionary<Local, string>
|
||||
{
|
||||
{ Local.Chinese, "清除过滤器 \"{0}\"" },
|
||||
{ Local.Dutch, "Filter \"{0}\" verwijderen" },
|
||||
{ Local.English, "Clear filter \"{0}\"" },
|
||||
{ Local.French, "Effacer le filtre \"{0}\"" },
|
||||
{ Local.German, "Filter löschen \"{0}\"" },
|
||||
{ Local.Italian, "Cancella filtro \"{0}\"" },
|
||||
{ Local.Russian, "Очистить фильтр \"{0}\"" },
|
||||
}
|
||||
},
|
||||
|
||||
{
|
||||
"Contains", new Dictionary<Local, string>
|
||||
{
|
||||
{ Local.Chinese, "搜索(包含)" },
|
||||
{ Local.Dutch, "Zoek (bevat)" },
|
||||
{ Local.English, "Search (contains)" },
|
||||
{ Local.French, "Rechercher (contient)" },
|
||||
{ Local.German, "Suche (enthält)" },
|
||||
{ Local.Italian, "Cerca (contiene)" },
|
||||
{ Local.Russian, "Искать (содержит)" },
|
||||
}
|
||||
},
|
||||
|
||||
{
|
||||
"StartsWith", new Dictionary<Local, string>
|
||||
{
|
||||
{ Local.Chinese, "搜索 (来自)" },
|
||||
{ Local.Dutch, "Zoek (beginnen met)" },
|
||||
{ Local.English, "Search (startswith)" },
|
||||
{ Local.French, "Rechercher (commence par)" },
|
||||
{ Local.German, "Suche (beginnen mit)" },
|
||||
{ Local.Italian, "Cerca (inizia con)" },
|
||||
{ Local.Russian, "Искать (hачни с)" },
|
||||
}
|
||||
},
|
||||
|
||||
{
|
||||
"Toggle", new Dictionary<Local, string>
|
||||
{
|
||||
{ Local.Chinese, "切換包含/開始於" },
|
||||
{ Local.Dutch, "Toggle bevat/begint met" },
|
||||
{ Local.English, "Toggle contains/startswith" },
|
||||
{ Local.French, "Basculer contient/commence par" },
|
||||
{ Local.German, "Toggle enthält/beginnt mit" },
|
||||
{ Local.Italian, "Toggle contiene/inizia con" },
|
||||
{ Local.Russian, "Переключить содержит/начинается с" },
|
||||
}
|
||||
},
|
||||
|
||||
{
|
||||
"Ok", new Dictionary<Local, string>
|
||||
{
|
||||
{ Local.Chinese, "确定" },
|
||||
{ Local.Dutch, "Ok" },
|
||||
{ Local.English, "Ok" },
|
||||
{ Local.French, "Ok" },
|
||||
{ Local.German, "Ok" },
|
||||
{ Local.Italian, "Ok" },
|
||||
{ Local.Russian, "Ok" },
|
||||
}
|
||||
},
|
||||
{
|
||||
"Cancel", new Dictionary<Local, string>
|
||||
{
|
||||
{ Local.Chinese, "取消" },
|
||||
{ Local.Dutch, "Annuleren" },
|
||||
{ Local.English, "Cancel" },
|
||||
{ Local.French, "Annuler" },
|
||||
{ Local.German, "Abbrechen" },
|
||||
{ Local.Italian, "Annulla" },
|
||||
{ Local.Russian, "Отмена" },
|
||||
}
|
||||
},
|
||||
{
|
||||
"Status", new Dictionary<Local, string>
|
||||
{
|
||||
{ Local.Chinese, "{0:n0} 找到了 {1:n0} 条记录" },
|
||||
{ Local.Dutch, "{0:n0} rij(en) gevonden op {1:n0}" },
|
||||
{ Local.English, "{0:n0} record(s) found on {1:n0}" },
|
||||
{ Local.French, "{0:n0} enregistrement(s) trouvé(s) sur {1:n0}" },
|
||||
{ Local.German, "{0:n0} zeilen angezeigt von {1:n0}" },
|
||||
{ Local.Italian, "{0:n0} oggetti trovati su {1:n0}" },
|
||||
{ Local.Russian, "{0:n0} записей найдено на {1:n0}" },
|
||||
}
|
||||
},
|
||||
{
|
||||
"ElapsedTime", new Dictionary<Local, string>
|
||||
{
|
||||
{ Local.Chinese, "经过时间{0:mm}:{0:ss}.{0:ff}" },
|
||||
{ Local.Dutch, "Verstreken tijd {0:mm}:{0:ss}.{0:ff}" },
|
||||
{ Local.English, "Elapsed time {0:mm}:{0:ss}.{0:ff}" },
|
||||
{ Local.French, "Temps écoulé {0:mm}:{0:ss}.{0:ff}" },
|
||||
{ Local.German, "Verstrichene Zeit {0:mm}:{0:ss}.{0:ff}" },
|
||||
{ Local.Italian, "Tempo trascorso {0:mm}:{0:ss}.{0:ff}" },
|
||||
{ Local.Russian, "Пройденное время {0:mm}:{0:ss}.{0:ff}" },
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
#endregion Private Fields
|
||||
|
||||
#region Constructors
|
||||
|
||||
public Loc()
|
||||
{
|
||||
Language = Local.English;
|
||||
}
|
||||
|
||||
#endregion Constructors
|
||||
|
||||
#region Public Properties
|
||||
|
||||
public Local Language
|
||||
{
|
||||
get => language;
|
||||
set
|
||||
{
|
||||
language = value;
|
||||
Culture = new CultureInfo(CultureNames[value]);
|
||||
}
|
||||
}
|
||||
|
||||
public CultureInfo Culture { get; private set; }
|
||||
|
||||
public string CultureName => CultureNames[Language];
|
||||
|
||||
public string LanguageName => Enum.GetName(typeof(Local), Language);
|
||||
|
||||
public string All => Translate("All");
|
||||
|
||||
public string Cancel => Translate("Cancel");
|
||||
|
||||
public string Clear => Translate("Clear");
|
||||
|
||||
public string Contains => Translate("Contains");
|
||||
|
||||
public string ElapsedTime => Translate("ElapsedTime");
|
||||
|
||||
public string Empty => Translate("Empty");
|
||||
|
||||
public string Ok => Translate("Ok");
|
||||
|
||||
public string StartsWith => Translate("StartsWith");
|
||||
|
||||
public string Status => Translate("Status");
|
||||
|
||||
public string Toggle => Translate("Toggle");
|
||||
|
||||
#endregion Public Properties
|
||||
|
||||
#region Private Methods
|
||||
|
||||
/// <summary>
|
||||
/// Translated into the language
|
||||
/// </summary>
|
||||
/// <param name="key"></param>
|
||||
/// <returns></returns>
|
||||
private string Translate(string key)
|
||||
{
|
||||
return Translation.ContainsKey(key) && Translation[key].ContainsKey(Language)
|
||||
? Translation[key][Language]
|
||||
: "unknow";
|
||||
}
|
||||
|
||||
#endregion Private Methods
|
||||
}
|
||||
}
|
||||
1
FSI.Lib/Wpf/Ctrls/FilterDataGrid/Readme.md
Normal file
1
FSI.Lib/Wpf/Ctrls/FilterDataGrid/Readme.md
Normal file
@@ -0,0 +1 @@
|
||||
https://github.com/macgile/DataGridFilter
|
||||
36
FSI.Lib/Wpf/Ctrls/FilterDataGrid/StringFormatConverter.cs
Normal file
36
FSI.Lib/Wpf/Ctrls/FilterDataGrid/StringFormatConverter.cs
Normal file
@@ -0,0 +1,36 @@
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Windows;
|
||||
using System.Windows.Data;
|
||||
|
||||
namespace FSI.Lib.Wpf.Ctrls.FilterDataGrid
|
||||
{
|
||||
public class StringFormatConverter : IMultiValueConverter
|
||||
{
|
||||
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
|
||||
{
|
||||
try
|
||||
{
|
||||
// values [0] contains the format
|
||||
if (values[0] == DependencyProperty.UnsetValue || string.IsNullOrEmpty(values[0]?.ToString()))
|
||||
return string.Empty;
|
||||
|
||||
var stringFormat = values[0].ToString();
|
||||
|
||||
return string.Format(stringFormat, values.Skip(1).ToArray());
|
||||
}
|
||||
catch (FormatException ex)
|
||||
{
|
||||
Debug.WriteLine($"StringFormatConverter.Convert error: {ex.Message}");
|
||||
return string.Empty;
|
||||
}
|
||||
}
|
||||
|
||||
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
|
||||
{
|
||||
throw new NotSupportedException();
|
||||
}
|
||||
}
|
||||
}
|
||||
880
FSI.Lib/Wpf/Ctrls/FilterDataGrid/Themes/FilterDataGrid.xaml
Normal file
880
FSI.Lib/Wpf/Ctrls/FilterDataGrid/Themes/FilterDataGrid.xaml
Normal file
@@ -0,0 +1,880 @@
|
||||
<ResourceDictionary x:Class="FSI.Lib.Wpf.Ctrls.FilterDataGrid.FilterDataGridDictionary"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:control="clr-namespace:FSI.Lib.Wpf.Ctrls.FilterDataGrid"
|
||||
xmlns:sys="clr-namespace:System;assembly=mscorlib">
|
||||
|
||||
<!-- STRING FORMAT CONVERTER -->
|
||||
<control:StringFormatConverter x:Key="StringFormatConverter" />
|
||||
|
||||
<!-- INITIAL POPUP SIZE -->
|
||||
<sys:Double x:Key="PopupHeight">420</sys:Double>
|
||||
<sys:Double x:Key="PopupWidth">262</sys:Double>
|
||||
<sys:Boolean x:Key="StayOpen">False</sys:Boolean>
|
||||
|
||||
<!-- https://yqnn.github.io/svg-path-editor/ -->
|
||||
|
||||
<!-- FILTER SET ICON -->
|
||||
<Geometry x:Key="FilterSet">
|
||||
M 0 17 H 12 L 6 25 Z M 6 0 H 29 L 29 3 L 20 10 L 20 21 H 15 L 15 10 L 6 3 Z
|
||||
</Geometry>
|
||||
|
||||
<!-- FILTER BUTTON ICON -->
|
||||
<Geometry x:Key="Filter">
|
||||
M 7 10 L 12 15 L 17 10 H 7 Z
|
||||
</Geometry>
|
||||
|
||||
<!-- DELETE FILTER ICON -->
|
||||
<Geometry x:Key="FilterDelete">
|
||||
M11.1 11.4L8.5 8.9L9.8 7.6L12.3 10.1L14.8 7.6L16.1 8.9L13.6 11.4L16.1 13.9L14.8 15.2L12.3 12.6L9.8 15.2L8.5 13.9ZM0 0H13L13 2L8 6V14L5 11V6L0 2Z
|
||||
</Geometry>
|
||||
|
||||
<!-- BOX CHECKED ICON -->
|
||||
<Geometry x:Key="FilterChecked">
|
||||
M 125 125 L 0 125 V 0 H 125 Z M 1 124 H 124 V 1 H 1 Z M 20 68 L 29 57 L 56 80 L 98 25 L 110 35 L 59 101 Z
|
||||
</Geometry>
|
||||
|
||||
<!-- GRIPSIZE ICON -->
|
||||
<Geometry x:Key="GripSizeIcon">
|
||||
M0 9L2 9M4 9L6 9M8 9L10 9M1 8L1 10M5 8L5 10M9 8L9 10M4 5L6 5M8 5L10 5M5 4L5 6M9 4L9 6M8 1L10 1M9 0L9 2
|
||||
</Geometry>
|
||||
|
||||
<!-- SEARCH MAGNIFIER ICON -->
|
||||
<Geometry x:Key="Magnifier">
|
||||
M9.6 8.5H9L8.7 8.2C9.6 7.4 10 6.2 10 5C10 2.2 7.8 0 5 0S0 2.2 0 5S2.2 10 5 10C6.2 10 7.4 9.6 8.2 8.7L8.5
|
||||
9V9.6L12.3 13.5L13.5 12.3L9.6 8.5ZM5 8.5C3.1 8.5 1.5 6.9 1.5 5S3.1 1.5 5 1.5S8.5 3.1 8.5 5S6.9 8.5 5 8.5Z
|
||||
</Geometry>
|
||||
|
||||
<!-- SEARCH DELETE ICON -->
|
||||
<Geometry x:Key="Delete">
|
||||
M 0 0 M 2 3 L 3 2 L 8 7 L 13 2 L 14 3 L 9 8 L 14 13 L 13 14 L 8 9 L 3 14 L 2 13 L 7 8 Z M 16 0 M 0 16 M 16 16
|
||||
</Geometry>
|
||||
|
||||
<!-- SEARCH CONTAINS ICON -->
|
||||
<Geometry x:Key="StartsWith">
|
||||
M 0.6 1.3 V 0 C 1.1 -0.1 5.5 -1.7 6.2 1.6 V 7.2 H 5 V 6.2 C 4.5 6.7 4 7 3.5 7.2 H 1.4 C 0.4 6.8 0.1 6.1 0 5.2 C 0 4.7 0 3 3 2.8 H 4.9 V 1.7
|
||||
C 4.4 0.2 2.3 0.6 1 1.1 Z M 4.9 5.1 V 3.8 H 2.6 V 3.8 C 1 3.9 0.9 6.2 2.6 6.2 C 3.5 6.2 4 5.9 4.7 5.3 Z M 9.3 0.9 L 9.5 3.5 L 7.4 2 L 6.9 2.9
|
||||
L 9.2 4 L 6.9 5.1 L 7.3 6.1 L 9.5 4.7 L 9.3 7.2 H 10.4 L 10.2 4.6 L 12.3 6 L 12.8 5.1 L 10.5 4.1 L 12.8 2.9 L 12.3 2 L 10.2 3.5 L 10.4 0.9 Z
|
||||
M 16.5 0.9 H 17.6 L 17.4 3.4 L 19.5 2 L 20 2.9 L 17.7 4 L 20 5.2 L 19.5 6 L 17.4 4.7 L 17.6 7.2 H 16.5 L 16.6 4.7 L 14.5 6 L 14 5.2 L 16.3 4
|
||||
L 14 2.9 L 14.6 2 L 16.7 3.4 Z
|
||||
</Geometry>
|
||||
|
||||
<!-- SEARCH STARTSWITH ICON -->
|
||||
<Geometry x:Key="Contains">
|
||||
M 7.2 1.3 V 0 C 7.7 -0.1 12.1 -1.7 12.8 1.6 V 7.2 H 11.6 V 6.2 C 11.1 6.7 10.6 7 10.1 7.2 H 8 C 7 6.8 6.7 6.1 6.6 5.2 C 6.6 4.7 6.6 3 9.6 2.8
|
||||
H 11.5 V 1.7 C 11 0.2 8.9 0.6 7.6 1.1 Z M 11.5 5.1 V 3.8 H 9.2 C 7.5 4.1 8.1 6.2 9.2 6.2 C 10.1 6.2 10.6 5.9 11.3 5.3 Z M 2.4 1 L 2.6 3.5
|
||||
L 0.5 2.1 L 0 3 L 2.2 4.1 L 0 5.3 L 0.4 6.1 L 2.6 4.7 L 2.4 7.2 H 3.5 L 3.3 4.7 L 5.4 6.1 L 5.9 5.2 L 3.6 4.1 L 5.9 3 L 5.4 2.1 L 3.3 3.5
|
||||
L 3.5 1 Z M 16.5 0.9 H 17.6 L 17.4 3.5 L 19.5 2 L 20 3 L 17.7 4 L 20 5.2 L 19.5 6 L 17.4 4.7 L 17.6 7.2 H 16.5 L 16.6 4.7 L 14.5 6 L 14 5.2
|
||||
L 16.3 4.1 L 14 2.9 L 14.6 2 L 16.6 3.4 Z
|
||||
</Geometry>
|
||||
|
||||
<!-- PLACEHOLDER SEARCH BOX -->
|
||||
<Style x:Key="PlaceHolder"
|
||||
TargetType="{x:Type TextBox}">
|
||||
<Setter Property="Background"
|
||||
Value="Transparent" />
|
||||
<Setter Property="BorderBrush"
|
||||
Value="Transparent" />
|
||||
<Setter Property="BorderThickness"
|
||||
Value="0" />
|
||||
<Setter Property="Template">
|
||||
<Setter.Value>
|
||||
<ControlTemplate x:Name="SearchControlTemplate"
|
||||
TargetType="{x:Type TextBox}">
|
||||
<Grid>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="*" />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<!-- SEARCH TEXTBOX -->
|
||||
<TextBox x:Name="TextSource"
|
||||
Grid.Column="0"
|
||||
Margin="{TemplateBinding Margin}"
|
||||
Padding="{TemplateBinding Padding}"
|
||||
HorizontalAlignment="{TemplateBinding HorizontalAlignment}"
|
||||
VerticalAlignment="{TemplateBinding VerticalAlignment}"
|
||||
VerticalContentAlignment="{TemplateBinding VerticalContentAlignment}"
|
||||
Panel.ZIndex="2"
|
||||
Background="Transparent"
|
||||
BorderBrush="Transparent"
|
||||
BorderThickness="0"
|
||||
Focusable="True"
|
||||
FontSize="{TemplateBinding FontSize}"
|
||||
FontWeight="{TemplateBinding FontWeight}"
|
||||
MaxLength="{TemplateBinding MaxLength}"
|
||||
Text="{Binding Path=Text, RelativeSource={RelativeSource TemplatedParent}, UpdateSourceTrigger=PropertyChanged}" />
|
||||
<TextBox x:Name="TextBoxPlaceHolder"
|
||||
Grid.Column="0"
|
||||
Margin="{TemplateBinding Margin}"
|
||||
Padding="{TemplateBinding Padding}"
|
||||
HorizontalAlignment="{TemplateBinding HorizontalAlignment}"
|
||||
VerticalAlignment="{TemplateBinding VerticalAlignment}"
|
||||
VerticalContentAlignment="{TemplateBinding VerticalContentAlignment}"
|
||||
Panel.ZIndex="1"
|
||||
Background="Transparent"
|
||||
BorderBrush="Transparent"
|
||||
BorderThickness="0"
|
||||
FontSize="{TemplateBinding FontSize}"
|
||||
FontWeight="{TemplateBinding FontWeight}"
|
||||
MaxLength="{TemplateBinding MaxLength}"
|
||||
Text="{TemplateBinding Tag}">
|
||||
<TextBox.Style>
|
||||
<Style TargetType="{x:Type TextBox}">
|
||||
<Setter Property="Foreground"
|
||||
Value="Transparent" />
|
||||
<Style.Triggers>
|
||||
<DataTrigger Binding="{Binding Path=Text, ElementName=TextSource}"
|
||||
Value="">
|
||||
<Setter Property="Foreground"
|
||||
Value="LightGray" />
|
||||
</DataTrigger>
|
||||
</Style.Triggers>
|
||||
</Style>
|
||||
</TextBox.Style>
|
||||
</TextBox>
|
||||
|
||||
<!-- BUTTON CLEAR FILTER -->
|
||||
<Button x:Name="ClearSearchBoxBtn"
|
||||
Grid.Column="1"
|
||||
Margin="2"
|
||||
Background="Transparent"
|
||||
Command="{x:Static control:FilterDataGrid.ClearSearchBox}"
|
||||
SnapsToDevicePixels="True"
|
||||
UseLayoutRounding="True">
|
||||
<Button.Style>
|
||||
<Style TargetType="{x:Type Button}">
|
||||
<Setter Property="Template">
|
||||
<Setter.Value>
|
||||
<ControlTemplate TargetType="Button">
|
||||
<Border Background="Transparent"
|
||||
SnapsToDevicePixels="True">
|
||||
<!-- MAGNIFIER / DELETE ICON -->
|
||||
<Path x:Name="PathButton"
|
||||
Width="18"
|
||||
Height="18"
|
||||
Margin="0"
|
||||
Data="{StaticResource Delete}"
|
||||
Fill="DarkSlateGray"
|
||||
SnapsToDevicePixels="True"
|
||||
Stretch="Uniform"
|
||||
UseLayoutRounding="True" />
|
||||
</Border>
|
||||
<ControlTemplate.Triggers>
|
||||
<DataTrigger Binding="{Binding ElementName=TextSource, Path=Text}"
|
||||
Value="">
|
||||
<Setter Property="IsEnabled"
|
||||
Value="False" />
|
||||
<Setter TargetName="PathButton"
|
||||
Property="Data"
|
||||
Value="{StaticResource Magnifier}" />
|
||||
</DataTrigger>
|
||||
<Trigger Property="IsMouseOver"
|
||||
Value="True">
|
||||
<Setter Property="Cursor"
|
||||
Value="Hand" />
|
||||
<Setter TargetName="PathButton"
|
||||
Property="Fill"
|
||||
Value="Red" />
|
||||
<Setter TargetName="PathButton"
|
||||
Property="Stroke"
|
||||
Value="Red" />
|
||||
</Trigger>
|
||||
</ControlTemplate.Triggers>
|
||||
</ControlTemplate>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Style>
|
||||
</Button.Style>
|
||||
</Button>
|
||||
|
||||
<!-- SEPARATOR -->
|
||||
<Border Grid.Column="2"
|
||||
Width="1"
|
||||
Margin="2,0,2,0"
|
||||
Background="LightGray" />
|
||||
|
||||
<!-- TOGGLE BUTTON -->
|
||||
<ToggleButton Name="SearchToggleButton"
|
||||
Grid.Column="3"
|
||||
Margin="2,0,2,0"
|
||||
VerticalAlignment="Stretch"
|
||||
Background="Transparent"
|
||||
DataContext="{Binding RelativeSource={RelativeSource AncestorType={x:Type control:FilterDataGrid}}}"
|
||||
IsChecked="{Binding StartsWith, UpdateSourceTrigger=PropertyChanged}"
|
||||
ToolTip="{Binding Translate.Toggle}">
|
||||
<ToggleButton.Style>
|
||||
<Style TargetType="{x:Type ToggleButton}">
|
||||
<Setter Property="Cursor"
|
||||
Value="Hand" />
|
||||
<Setter Property="Template">
|
||||
<Setter.Value>
|
||||
<ControlTemplate TargetType="ToggleButton">
|
||||
<Border Padding="3"
|
||||
Background="Transparent"
|
||||
BorderBrush="LightGray"
|
||||
BorderThickness="0">
|
||||
<Path x:Name="PathToggle"
|
||||
VerticalAlignment="Center"
|
||||
Data="{StaticResource Contains}"
|
||||
Fill="DarkSlateGray"
|
||||
Stretch="UniformToFill" />
|
||||
</Border>
|
||||
<ControlTemplate.Triggers>
|
||||
<Trigger Property="IsChecked"
|
||||
Value="True">
|
||||
<Setter TargetName="PathToggle"
|
||||
Property="Data"
|
||||
Value="{StaticResource StartsWith}" />
|
||||
</Trigger>
|
||||
<Trigger Property="IsMouseOver"
|
||||
Value="True">
|
||||
<Setter TargetName="PathToggle"
|
||||
Property="Fill"
|
||||
Value="Black" />
|
||||
</Trigger>
|
||||
</ControlTemplate.Triggers>
|
||||
</ControlTemplate>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Style>
|
||||
</ToggleButton.Style>
|
||||
</ToggleButton>
|
||||
</Grid>
|
||||
<!-- PLACEHOLDER TEXT -->
|
||||
<ControlTemplate.Triggers>
|
||||
<Trigger Property="IsFocused"
|
||||
Value="True">
|
||||
<Setter TargetName="TextSource"
|
||||
Property="FocusManager.FocusedElement"
|
||||
Value="{Binding RelativeSource={RelativeSource Self}}" />
|
||||
</Trigger>
|
||||
<DataTrigger Binding="{Binding ElementName=SearchToggleButton, Path=IsChecked}"
|
||||
Value="True">
|
||||
<Setter TargetName="TextBoxPlaceHolder"
|
||||
Property="Text"
|
||||
Value="{Binding Translate.StartsWith, Mode=OneWay}" />
|
||||
</DataTrigger>
|
||||
<DataTrigger Binding="{Binding ElementName=SearchToggleButton, Path=IsChecked}"
|
||||
Value="False">
|
||||
<Setter TargetName="TextBoxPlaceHolder"
|
||||
Property="Text"
|
||||
Value="{Binding Translate.Contains, Mode=OneWay}" />
|
||||
</DataTrigger>
|
||||
</ControlTemplate.Triggers>
|
||||
</ControlTemplate>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Style>
|
||||
|
||||
<!-- DEFAULT STYLE AND CONTROLTEMPLATE FOR DATAGRID -->
|
||||
<Style BasedOn="{StaticResource {x:Type DataGrid}}"
|
||||
TargetType="{x:Type control:FilterDataGrid}">
|
||||
|
||||
<!-- DISABLING CanUserAddRows : AggregateException when user can add row -->
|
||||
<Setter Property="CanUserAddRows"
|
||||
Value="False" />
|
||||
|
||||
<!-- ROWS COUNT TEMPLATE -->
|
||||
<Setter Property="RowHeaderTemplate">
|
||||
<Setter.Value>
|
||||
<DataTemplate>
|
||||
<TextBlock Text="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type DataGridRow}}, Path=Header}" />
|
||||
</DataTemplate>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
<Setter Property="Template">
|
||||
<Setter.Value>
|
||||
<ControlTemplate TargetType="{x:Type DataGrid}">
|
||||
<Border Padding="{TemplateBinding Padding}"
|
||||
Background="{TemplateBinding Background}"
|
||||
BorderBrush="{TemplateBinding BorderBrush}"
|
||||
BorderThickness="{TemplateBinding BorderThickness}"
|
||||
SnapsToDevicePixels="True">
|
||||
<Grid>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="*" />
|
||||
<RowDefinition Height="Auto" />
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<!-- CONTENT DATAGRID -->
|
||||
<ScrollViewer x:Name="DG_ScrollViewer"
|
||||
Grid.Row="0"
|
||||
CanContentScroll="True"
|
||||
Focusable="false">
|
||||
<ScrollViewer.Template>
|
||||
<ControlTemplate TargetType="{x:Type ScrollViewer}">
|
||||
<Grid ShowGridLines="False">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto" />
|
||||
<ColumnDefinition Width="*" />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="*" />
|
||||
<RowDefinition Height="Auto" />
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<Button Width="{Binding CellsPanelHorizontalOffset, RelativeSource={RelativeSource AncestorType={x:Type DataGrid}}}"
|
||||
Command="{x:Static DataGrid.SelectAllCommand}"
|
||||
Focusable="false"
|
||||
Style="{DynamicResource {ComponentResourceKey ResourceId=DataGridSelectAllButtonStyle,
|
||||
TypeInTargetAssembly={x:Type DataGrid}}}"
|
||||
Visibility="{Binding HeadersVisibility, ConverterParameter={x:Static DataGridHeadersVisibility.All}, Converter={x:Static DataGrid.HeadersVisibilityConverter}, RelativeSource={RelativeSource AncestorType={x:Type DataGrid}}}" />
|
||||
<DataGridColumnHeadersPresenter x:Name="PART_ColumnHeadersPresenter"
|
||||
Grid.Row="0"
|
||||
Grid.Column="1"
|
||||
Visibility="{Binding HeadersVisibility, ConverterParameter={x:Static DataGridHeadersVisibility.Column}, Converter={x:Static DataGrid.HeadersVisibilityConverter}, RelativeSource={RelativeSource AncestorType={x:Type DataGrid}}}" />
|
||||
<ScrollContentPresenter x:Name="PART_ScrollContentPresenter"
|
||||
Grid.Row="1"
|
||||
Grid.Column="0"
|
||||
Grid.ColumnSpan="2"
|
||||
CanContentScroll="{TemplateBinding CanContentScroll}" />
|
||||
<ScrollBar x:Name="PART_VerticalScrollBar"
|
||||
Grid.Row="1"
|
||||
Grid.Column="2"
|
||||
Maximum="{TemplateBinding ScrollableHeight}"
|
||||
Orientation="Vertical"
|
||||
ViewportSize="{TemplateBinding ViewportHeight}"
|
||||
Visibility="{TemplateBinding ComputedVerticalScrollBarVisibility}"
|
||||
Value="{Binding VerticalOffset, Mode=OneWay, RelativeSource={RelativeSource TemplatedParent}}" />
|
||||
<Grid Grid.Row="2"
|
||||
Grid.Column="1">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="{Binding NonFrozenColumnsViewportHorizontalOffset, RelativeSource={RelativeSource AncestorType={x:Type DataGrid}}}" />
|
||||
<ColumnDefinition Width="*" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<ScrollBar x:Name="PART_HorizontalScrollBar"
|
||||
Grid.Column="1"
|
||||
Maximum="{TemplateBinding ScrollableWidth}"
|
||||
Orientation="Horizontal"
|
||||
ViewportSize="{TemplateBinding ViewportWidth}"
|
||||
Visibility="{TemplateBinding ComputedHorizontalScrollBarVisibility}"
|
||||
Value="{Binding HorizontalOffset, Mode=OneWay, RelativeSource={RelativeSource TemplatedParent}}" />
|
||||
</Grid>
|
||||
</Grid>
|
||||
</ControlTemplate>
|
||||
</ScrollViewer.Template>
|
||||
<ItemsPresenter SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}" />
|
||||
</ScrollViewer>
|
||||
|
||||
<!-- STATUS BAR & RESULT FILTER -->
|
||||
<Border x:Name="BorderStatusBar"
|
||||
Grid.Row="1"
|
||||
Padding="4,2"
|
||||
Background="Transparent"
|
||||
BorderBrush="LightGray"
|
||||
BorderThickness="0,1">
|
||||
<Border.Style>
|
||||
<Style TargetType="Border">
|
||||
<Setter Property="Visibility"
|
||||
Value="Collapsed" />
|
||||
<Style.Triggers>
|
||||
<DataTrigger Binding="{Binding Path=ShowStatusBar, RelativeSource={RelativeSource AncestorType={x:Type control:FilterDataGrid}}}"
|
||||
Value="True">
|
||||
<Setter Property="Visibility"
|
||||
Value="Visible" />
|
||||
</DataTrigger>
|
||||
</Style.Triggers>
|
||||
</Style>
|
||||
</Border.Style>
|
||||
|
||||
<UniformGrid Columns="2"
|
||||
DataContext="{Binding RelativeSource={RelativeSource AncestorType={x:Type control:FilterDataGrid}}}">
|
||||
|
||||
<!-- RESULT STATUS -->
|
||||
<TextBlock HorizontalAlignment="Left">
|
||||
<TextBlock.Text>
|
||||
<MultiBinding Converter="{StaticResource StringFormatConverter}">
|
||||
<Binding Path="Translate.Status" />
|
||||
<Binding Path="Items.Count"
|
||||
UpdateSourceTrigger="PropertyChanged" />
|
||||
<Binding Path="ItemsSourceCount" />
|
||||
</MultiBinding>
|
||||
</TextBlock.Text>
|
||||
</TextBlock>
|
||||
|
||||
<!-- ELAPSED TIME -->
|
||||
<TextBlock HorizontalAlignment="Right">
|
||||
<TextBlock.Text>
|
||||
<MultiBinding Converter="{StaticResource StringFormatConverter}">
|
||||
<Binding Path="Translate.ElapsedTime"
|
||||
UpdateSourceTrigger="PropertyChanged" />
|
||||
<Binding Path="ElapsedTime" />
|
||||
</MultiBinding>
|
||||
</TextBlock.Text>
|
||||
<TextBlock.Style>
|
||||
<Style TargetType="TextBlock">
|
||||
<Setter Property="Visibility"
|
||||
Value="Collapsed" />
|
||||
<Style.Triggers>
|
||||
<DataTrigger Binding="{Binding ShowElapsedTime}"
|
||||
Value="True">
|
||||
<Setter Property="Visibility"
|
||||
Value="Visible" />
|
||||
</DataTrigger>
|
||||
</Style.Triggers>
|
||||
</Style>
|
||||
</TextBlock.Style>
|
||||
</TextBlock>
|
||||
</UniformGrid>
|
||||
</Border>
|
||||
</Grid>
|
||||
</Border>
|
||||
</ControlTemplate>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Style>
|
||||
|
||||
<!-- DATAGRIDCOLUMNHEADER STYLE -->
|
||||
<Style BasedOn="{StaticResource {x:Type DataGridColumnHeader}}"
|
||||
TargetType="DataGridColumnHeader">
|
||||
<Setter Property="HorizontalContentAlignment"
|
||||
Value="Stretch" />
|
||||
</Style>
|
||||
|
||||
<!-- DATATEMPLATE OF DATAGRIDCOLUMNHEADER -->
|
||||
<DataTemplate x:Key="DataGridHeaderTemplate">
|
||||
|
||||
<!-- HEADER STRECH TO CONTENTPRESENTER OF DATAGRIDCOLUMNHEADER -->
|
||||
<Grid x:Name="ContainerFilterGrid"
|
||||
Width="{Binding RelativeSource={RelativeSource AncestorType={x:Type ContentPresenter}, Mode=FindAncestor, AncestorLevel=1}, Path=ActualWidth}"
|
||||
HorizontalAlignment="Stretch"
|
||||
Background="Transparent">
|
||||
|
||||
<!-- HEADER/BUTTON -->
|
||||
<Grid x:Name="GridHeaderButton"
|
||||
ShowGridLines="False">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="*" />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<!-- RENDER THE HEADER TEXT -->
|
||||
<TextBlock Grid.Column="0"
|
||||
Text="{Binding}" />
|
||||
|
||||
<!-- FILTER BUTTON -->
|
||||
<Button Name="filterButton"
|
||||
Grid.Column="1"
|
||||
Width="19"
|
||||
Height="19"
|
||||
Background="Transparent"
|
||||
BorderBrush="DarkGray"
|
||||
BorderThickness="1"
|
||||
Command="{x:Static control:FilterDataGrid.ShowFilter}"
|
||||
Cursor="Hand"
|
||||
Opacity="0.5"
|
||||
OverridesDefaultStyle="True"
|
||||
SnapsToDevicePixels="True"
|
||||
UseLayoutRounding="True">
|
||||
<Button.Style>
|
||||
<Style TargetType="{x:Type Button}">
|
||||
<Setter Property="Template">
|
||||
<Setter.Value>
|
||||
<ControlTemplate TargetType="{x:Type Button}">
|
||||
<Border Padding="2"
|
||||
Background="{TemplateBinding Background}"
|
||||
BorderBrush="{TemplateBinding BorderBrush}"
|
||||
BorderThickness="1"
|
||||
SnapsToDevicePixels="True"
|
||||
UseLayoutRounding="True">
|
||||
<Path x:Name="PathFilterIcon"
|
||||
Margin="0"
|
||||
HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center"
|
||||
Data="{StaticResource Filter}"
|
||||
Fill="DarkSlateGray"
|
||||
Stretch="Uniform" />
|
||||
</Border>
|
||||
</ControlTemplate>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Style>
|
||||
</Button.Style>
|
||||
</Button>
|
||||
</Grid>
|
||||
|
||||
<!-- POPUP -->
|
||||
<Popup Name="FilterPopup"
|
||||
AllowsTransparency="True"
|
||||
IsOpen="False"
|
||||
PlacementTarget="{Binding ElementName=ContainerFilterGrid}"
|
||||
StaysOpen="{StaticResource StayOpen}">
|
||||
|
||||
<Border x:Name="PopUpBorder"
|
||||
Padding="0"
|
||||
Background="{DynamicResource {x:Static SystemColors.WindowBrushKey}}"
|
||||
BorderBrush="LightGray"
|
||||
BorderThickness="1">
|
||||
|
||||
<Grid x:Name="SizableContentGrid"
|
||||
MinWidth="{StaticResource PopupWidth}"
|
||||
MinHeight="{StaticResource PopupHeight}"
|
||||
ShowGridLines="False"
|
||||
ZIndex="1">
|
||||
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto"
|
||||
MinWidth="32" />
|
||||
<ColumnDefinition Width="*" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="*" />
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<!-- BUTTON CLEAR FILTER -->
|
||||
<Button x:Name="ClearFilterBnt"
|
||||
Grid.Row="2"
|
||||
Grid.Column="0"
|
||||
Grid.ColumnSpan="2"
|
||||
Margin="2,10,2,2"
|
||||
Padding="4"
|
||||
HorizontalAlignment="Stretch"
|
||||
Command="{x:Static control:FilterDataGrid.RemoveFilter}"
|
||||
Content="{Binding Path=Content, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type DataGridColumnHeader}}, UpdateSourceTrigger=PropertyChanged}"
|
||||
FontSize="13"
|
||||
OverridesDefaultStyle="True">
|
||||
<Button.Style>
|
||||
<Style TargetType="{x:Type Button}">
|
||||
<Setter Property="Foreground"
|
||||
Value="DarkSlateGray" />
|
||||
<Setter Property="SnapsToDevicePixels"
|
||||
Value="True" />
|
||||
<Setter Property="Template">
|
||||
<Setter.Value>
|
||||
<ControlTemplate TargetType="{x:Type Button}">
|
||||
<Border x:Name="BorderContent"
|
||||
Padding="{TemplateBinding Padding}"
|
||||
BorderBrush="Transparent"
|
||||
BorderThickness="0"
|
||||
SnapsToDevicePixels="True"
|
||||
UseLayoutRounding="True">
|
||||
<Grid x:Name="ContentGrid"
|
||||
Background="Transparent">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto" />
|
||||
<ColumnDefinition Width="*" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<Path Grid.Column="0"
|
||||
Width="16"
|
||||
Margin="0,0,10,0"
|
||||
Data="{StaticResource FilterDelete}"
|
||||
Fill="{TemplateBinding Foreground}"
|
||||
Stretch="Uniform" />
|
||||
|
||||
<TextBlock x:Name="ContentPresenter"
|
||||
Grid.Column="1"
|
||||
HorizontalAlignment="Stretch"
|
||||
DataContext="{Binding RelativeSource={RelativeSource AncestorType={x:Type control:FilterDataGrid}}}">
|
||||
<TextBlock.Text>
|
||||
<MultiBinding Converter="{StaticResource StringFormatConverter}">
|
||||
<MultiBinding.Bindings>
|
||||
<Binding Path="Translate.Clear"
|
||||
TargetNullValue=""
|
||||
UpdateSourceTrigger="PropertyChanged" />
|
||||
<Binding ElementName="ClearFilterBnt"
|
||||
Path="Content" />
|
||||
</MultiBinding.Bindings>
|
||||
</MultiBinding>
|
||||
</TextBlock.Text>
|
||||
</TextBlock>
|
||||
</Grid>
|
||||
</Border>
|
||||
<ControlTemplate.Triggers>
|
||||
<Trigger Property="IsMouseOver"
|
||||
Value="True">
|
||||
<Setter TargetName="BorderContent"
|
||||
Property="Background"
|
||||
Value="#F0F0F0" />
|
||||
</Trigger>
|
||||
<Trigger Property="IsPressed"
|
||||
Value="True">
|
||||
<Setter TargetName="BorderContent"
|
||||
Property="Background">
|
||||
<Setter.Value>
|
||||
<SolidColorBrush Opacity="0.8"
|
||||
Color="LightGray" />
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Trigger>
|
||||
<Trigger Property="IsEnabled"
|
||||
Value="False">
|
||||
<Setter Property="Foreground">
|
||||
<Setter.Value>
|
||||
<SolidColorBrush Opacity="0.5"
|
||||
Color="DarkSlateGray" />
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Trigger>
|
||||
</ControlTemplate.Triggers>
|
||||
</ControlTemplate>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Style>
|
||||
</Button.Style>
|
||||
</Button>
|
||||
|
||||
<!-- SEPARATOR -->
|
||||
<Separator Grid.Row="3"
|
||||
Grid.Column="1"
|
||||
Margin="0,2"
|
||||
Background="LightGray" />
|
||||
|
||||
<!-- SEARCH BOX -->
|
||||
<Border Grid.Row="4"
|
||||
Grid.Column="1"
|
||||
Margin="0,4,4,2"
|
||||
Padding="0,2"
|
||||
VerticalAlignment="Top"
|
||||
Background="Transparent"
|
||||
BorderBrush="LightGray"
|
||||
BorderThickness="1">
|
||||
<Grid>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="*" />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<TextBox x:Name="SearchBox"
|
||||
Grid.Column="0"
|
||||
Margin="0"
|
||||
Padding="2,0,0,0"
|
||||
HorizontalAlignment="Stretch"
|
||||
VerticalContentAlignment="Center"
|
||||
AcceptsReturn="False"
|
||||
DataContext="{Binding RelativeSource={RelativeSource AncestorType={x:Type control:FilterDataGrid}}}"
|
||||
Focusable="True"
|
||||
FontSize="13"
|
||||
MaxLength="20"
|
||||
Style="{StaticResource PlaceHolder}"
|
||||
Tag="{Binding Translate.Contains}">
|
||||
<TextBox.InputBindings>
|
||||
<KeyBinding Key="Enter"
|
||||
Command="{x:Static control:FilterDataGrid.ApplyFilter}" />
|
||||
</TextBox.InputBindings>
|
||||
</TextBox>
|
||||
</Grid>
|
||||
</Border>
|
||||
|
||||
<!-- ICON (current filter is set) -->
|
||||
<Path x:Name="PathIsFiltered"
|
||||
Grid.Row="5"
|
||||
Grid.Column="0"
|
||||
Width="19"
|
||||
Height="20"
|
||||
Margin="0,5,0,0"
|
||||
VerticalAlignment="Top"
|
||||
Data="{StaticResource FilterChecked}"
|
||||
Fill="DarkSlateGray"
|
||||
Stretch="Fill"
|
||||
Stroke="DarkSlateGray"
|
||||
StrokeThickness="0.2">
|
||||
<Path.Style>
|
||||
<Style TargetType="Path">
|
||||
<Setter Property="Visibility"
|
||||
Value="Hidden" />
|
||||
<Style.Triggers>
|
||||
<DataTrigger Binding="{Binding ElementName=ClearFilterBnt, Path=IsEnabled}"
|
||||
Value="True">
|
||||
<Setter Property="Visibility"
|
||||
Value="Visible" />
|
||||
</DataTrigger>
|
||||
</Style.Triggers>
|
||||
</Style>
|
||||
</Path.Style>
|
||||
</Path>
|
||||
|
||||
<!-- LISTBOX / TREEVIEW -->
|
||||
<Border Grid.Row="5"
|
||||
Grid.Column="1"
|
||||
Margin="0,4,4,4"
|
||||
BorderThickness="0">
|
||||
|
||||
<Grid x:Name="GridItemControl">
|
||||
|
||||
<ListBox x:Name="PopupListBox"
|
||||
Grid.Row="0"
|
||||
Padding="2"
|
||||
HorizontalAlignment="Stretch"
|
||||
VerticalAlignment="Stretch"
|
||||
BorderBrush="LightGray"
|
||||
BorderThickness="1"
|
||||
ScrollViewer.HorizontalScrollBarVisibility="Auto"
|
||||
ScrollViewer.VerticalScrollBarVisibility="Auto"
|
||||
Visibility="Collapsed">
|
||||
<ListBox.ItemContainerStyle>
|
||||
<Style BasedOn="{StaticResource {x:Type ListBoxItem}}"
|
||||
TargetType="{x:Type ListBoxItem}">
|
||||
<Setter Property="HorizontalContentAlignment"
|
||||
Value="Left" />
|
||||
<Setter Property="VerticalContentAlignment"
|
||||
Value="Center" />
|
||||
</Style>
|
||||
</ListBox.ItemContainerStyle>
|
||||
<ListBox.ItemTemplate>
|
||||
<!--
|
||||
ContentStringFormat="{}{0}"
|
||||
Content="{Binding Label, ConverterCulture=en-US}"
|
||||
ContentStringFormat="F2, en-US"
|
||||
the value of the label is a string, so it cannot be formatted
|
||||
-->
|
||||
<DataTemplate DataType="{x:Type control:FilterItem}">
|
||||
<CheckBox x:Name="CheckBox"
|
||||
Width="Auto"
|
||||
HorizontalAlignment="Stretch"
|
||||
HorizontalContentAlignment="Stretch"
|
||||
Command="{x:Static control:FilterDataGrid.IsChecked}"
|
||||
CommandParameter="{Binding}"
|
||||
Content="{Binding Label}"
|
||||
FontWeight="Normal"
|
||||
IsChecked="{Binding IsChecked}"
|
||||
IsThreeState="False" />
|
||||
<DataTemplate.Triggers>
|
||||
<DataTrigger Binding="{Binding Level}"
|
||||
Value="1">
|
||||
<Setter TargetName="CheckBox"
|
||||
Property="Margin"
|
||||
Value="4,0,0,0" />
|
||||
</DataTrigger>
|
||||
</DataTemplate.Triggers>
|
||||
</DataTemplate>
|
||||
</ListBox.ItemTemplate>
|
||||
</ListBox>
|
||||
|
||||
<TreeView x:Name="PopupTreeview"
|
||||
Grid.Row="0"
|
||||
BorderBrush="LightGray"
|
||||
BorderThickness="1"
|
||||
ScrollViewer.HorizontalScrollBarVisibility="Auto"
|
||||
ScrollViewer.VerticalScrollBarVisibility="Auto"
|
||||
Visibility="Collapsed">
|
||||
<TreeView.ItemTemplate>
|
||||
<HierarchicalDataTemplate DataType="control:FilterItem"
|
||||
ItemsSource="{Binding Children}">
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<CheckBox x:Name="CheckBoxTree"
|
||||
VerticalAlignment="Center"
|
||||
Focusable="False"
|
||||
IsChecked="{Binding IsChecked, UpdateSourceTrigger=PropertyChanged}" />
|
||||
<ContentPresenter Margin="2"
|
||||
Content="{Binding Label}" />
|
||||
</StackPanel>
|
||||
<DataTemplate.Triggers>
|
||||
<DataTrigger Binding="{Binding Level}"
|
||||
Value="1">
|
||||
<Setter TargetName="CheckBoxTree"
|
||||
Property="Margin"
|
||||
Value="4,0,0,0" />
|
||||
</DataTrigger>
|
||||
</DataTemplate.Triggers>
|
||||
</HierarchicalDataTemplate>
|
||||
</TreeView.ItemTemplate>
|
||||
<TreeView.ItemContainerStyle>
|
||||
<Style BasedOn="{StaticResource {x:Type TreeViewItem}}"
|
||||
TargetType="{x:Type TreeViewItem}">
|
||||
<Setter Property="OverridesDefaultStyle"
|
||||
Value="True" />
|
||||
<Setter Property="IsExpanded"
|
||||
Value="False" />
|
||||
<Setter Property="HorizontalContentAlignment"
|
||||
Value="Stretch" />
|
||||
<Setter Property="VerticalContentAlignment"
|
||||
Value="Stretch" />
|
||||
<Setter Property="Visibility"
|
||||
Value="Visible" />
|
||||
</Style>
|
||||
</TreeView.ItemContainerStyle>
|
||||
</TreeView>
|
||||
</Grid>
|
||||
</Border>
|
||||
|
||||
<!-- OK/CANCEL BUTTON -->
|
||||
<UniformGrid Grid.Row="6"
|
||||
Grid.Column="1"
|
||||
Margin="0,6,4,6"
|
||||
HorizontalAlignment="Right"
|
||||
Background="Transparent"
|
||||
Columns="2"
|
||||
DataContext="{Binding RelativeSource={RelativeSource AncestorType={x:Type control:FilterDataGrid}}}">
|
||||
|
||||
<Button Width="100"
|
||||
Margin="0"
|
||||
HorizontalAlignment="Left"
|
||||
Command="{x:Static control:FilterDataGrid.ApplyFilter}"
|
||||
Content="{Binding Translate.Ok}" />
|
||||
|
||||
<Button Width="100"
|
||||
Margin="6,0,0,0"
|
||||
HorizontalAlignment="Right"
|
||||
Command="{x:Static control:FilterDataGrid.CancelFilter}"
|
||||
Content="{Binding Translate.Cancel}" />
|
||||
</UniformGrid>
|
||||
|
||||
<!-- RESIZE GRIP -->
|
||||
<Thumb x:Name="PopupThumb"
|
||||
Grid.Row="7"
|
||||
Grid.Column="0"
|
||||
Grid.ColumnSpan="2"
|
||||
Width="20"
|
||||
Height="Auto"
|
||||
Margin="0,0,2,2"
|
||||
HorizontalAlignment="Right"
|
||||
VerticalAlignment="Bottom"
|
||||
SnapsToDevicePixels="True"
|
||||
UseLayoutRounding="True">
|
||||
<Thumb.Style>
|
||||
<Style TargetType="{x:Type Thumb}">
|
||||
<Style.Setters>
|
||||
<Setter Property="Template">
|
||||
<Setter.Value>
|
||||
<ControlTemplate>
|
||||
<Grid x:Name="resizeVisual"
|
||||
HorizontalAlignment="Right"
|
||||
VerticalAlignment="Bottom"
|
||||
Background="Transparent"
|
||||
DockPanel.Dock="Right"
|
||||
SnapsToDevicePixels="True"
|
||||
UseLayoutRounding="True">
|
||||
<Path Width="12"
|
||||
Height="12"
|
||||
Margin="0"
|
||||
Data="{StaticResource GripSizeIcon}"
|
||||
Stretch="None"
|
||||
Stroke="LightSlateGray"
|
||||
StrokeThickness="1" />
|
||||
<Grid.Style>
|
||||
<Style TargetType="{x:Type Grid}">
|
||||
<Style.Triggers>
|
||||
<Trigger Property="IsMouseOver"
|
||||
Value="True">
|
||||
<Setter Property="Cursor"
|
||||
Value="SizeNWSE" />
|
||||
</Trigger>
|
||||
</Style.Triggers>
|
||||
</Style>
|
||||
</Grid.Style>
|
||||
</Grid>
|
||||
</ControlTemplate>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Style.Setters>
|
||||
</Style>
|
||||
</Thumb.Style>
|
||||
</Thumb>
|
||||
</Grid>
|
||||
</Border>
|
||||
</Popup>
|
||||
</Grid>
|
||||
</DataTemplate>
|
||||
</ResourceDictionary>
|
||||
18
FSI.Lib/Wpf/Ctrls/FilterDataGrid/Themes/Generic.xaml
Normal file
18
FSI.Lib/Wpf/Ctrls/FilterDataGrid/Themes/Generic.xaml
Normal file
@@ -0,0 +1,18 @@
|
||||
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:control="clr-namespace:FSI.Lib.Wpf.Ctrls.FilterDataGrid">
|
||||
|
||||
|
||||
<ResourceDictionary.MergedDictionaries>
|
||||
<ResourceDictionary Source="/FilterDataGrid;component/Themes/FilterDataGrid.xaml" />
|
||||
</ResourceDictionary.MergedDictionaries>
|
||||
|
||||
<!--
|
||||
EXTERNAL ACCESS FOR CUSTOMIZE DEFAULT STYLE
|
||||
SEE FILTERDATAGRID DEFAULT STYLE
|
||||
-->
|
||||
<Style x:Key="{ComponentResourceKey {x:Type control:FilterDataGrid},
|
||||
FilterDataGridStyle}"
|
||||
BasedOn="{StaticResource {x:Type control:FilterDataGrid}}"
|
||||
TargetType="{x:Type control:FilterDataGrid}" />
|
||||
</ResourceDictionary>
|
||||
138
FSI.Lib/Wpf/WindowExtensions.cs
Normal file
138
FSI.Lib/Wpf/WindowExtensions.cs
Normal file
@@ -0,0 +1,138 @@
|
||||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Windows;
|
||||
|
||||
namespace FSI.Lib.Wpf.ExtensionMethods
|
||||
{
|
||||
public static class WindowExtensions
|
||||
{
|
||||
#region Public Methods
|
||||
|
||||
public static bool ActivateCenteredToMouse(this Window window)
|
||||
{
|
||||
ComputeTopLeft(ref window);
|
||||
return window.Activate();
|
||||
}
|
||||
|
||||
public static void ShowCenteredToMouse(this Window window)
|
||||
{
|
||||
// in case the default start-up location isn't set to Manual
|
||||
WindowStartupLocation oldLocation = window.WindowStartupLocation;
|
||||
// set location to manual -> window will be placed by Top and Left property
|
||||
window.WindowStartupLocation = WindowStartupLocation.Manual;
|
||||
ComputeTopLeft(ref window);
|
||||
window.Show();
|
||||
window.WindowStartupLocation = oldLocation;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Methods
|
||||
|
||||
private static void ComputeTopLeft(ref Window window)
|
||||
{
|
||||
W32Point pt = new W32Point();
|
||||
if (!GetCursorPos(ref pt))
|
||||
{
|
||||
Marshal.ThrowExceptionForHR(Marshal.GetHRForLastWin32Error());
|
||||
}
|
||||
|
||||
// 0x00000002: return nearest monitor if pt is not contained in any monitor.
|
||||
IntPtr monHandle = MonitorFromPoint(pt, 0x00000002);
|
||||
W32MonitorInfo monInfo = new W32MonitorInfo();
|
||||
monInfo.Size = Marshal.SizeOf(typeof(W32MonitorInfo));
|
||||
|
||||
if (!GetMonitorInfo(monHandle, ref monInfo))
|
||||
{
|
||||
Marshal.ThrowExceptionForHR(Marshal.GetHRForLastWin32Error());
|
||||
}
|
||||
|
||||
// use WorkArea struct to include the taskbar position.
|
||||
W32Rect monitor = monInfo.WorkArea;
|
||||
double offsetX = Math.Round(window.Width / 2);
|
||||
double offsetY = Math.Round(window.Height / 2);
|
||||
|
||||
double top = pt.Y - offsetY;
|
||||
double left = pt.X - offsetX;
|
||||
|
||||
Rect screen = new Rect(
|
||||
new Point(monitor.Left, monitor.Top),
|
||||
new Point(monitor.Right, monitor.Bottom));
|
||||
Rect wnd = new Rect(
|
||||
new Point(left, top),
|
||||
new Point(left + window.Width, top + window.Height));
|
||||
|
||||
window.Top = wnd.Top;
|
||||
window.Left = wnd.Left;
|
||||
|
||||
if (!screen.Contains(wnd))
|
||||
{
|
||||
if (wnd.Top < screen.Top)
|
||||
{
|
||||
double diff = Math.Abs(screen.Top - wnd.Top);
|
||||
window.Top = wnd.Top + diff;
|
||||
}
|
||||
|
||||
if (wnd.Bottom > screen.Bottom)
|
||||
{
|
||||
double diff = wnd.Bottom - screen.Bottom;
|
||||
window.Top = wnd.Top - diff;
|
||||
}
|
||||
|
||||
if (wnd.Left < screen.Left)
|
||||
{
|
||||
double diff = Math.Abs(screen.Left - wnd.Left);
|
||||
window.Left = wnd.Left + diff;
|
||||
}
|
||||
|
||||
if (wnd.Right > screen.Right)
|
||||
{
|
||||
double diff = wnd.Right - screen.Right;
|
||||
window.Left = wnd.Left - diff;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region W32 API
|
||||
|
||||
[DllImport("user32.dll", SetLastError = true)]
|
||||
[return: MarshalAs(UnmanagedType.Bool)]
|
||||
private static extern bool GetCursorPos(ref W32Point pt);
|
||||
|
||||
[DllImport("user32.dll", SetLastError = true)]
|
||||
[return: MarshalAs(UnmanagedType.Bool)]
|
||||
private static extern bool GetMonitorInfo(IntPtr hMonitor, ref W32MonitorInfo lpmi);
|
||||
|
||||
[DllImport("user32.dll")]
|
||||
private static extern IntPtr MonitorFromPoint(W32Point pt, uint dwFlags);
|
||||
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
public struct W32Point
|
||||
{
|
||||
public int X;
|
||||
public int Y;
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
internal struct W32MonitorInfo
|
||||
{
|
||||
public int Size;
|
||||
public W32Rect Monitor;
|
||||
public W32Rect WorkArea;
|
||||
public uint Flags;
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
internal struct W32Rect
|
||||
{
|
||||
public int Left;
|
||||
public int Top;
|
||||
public int Right;
|
||||
public int Bottom;
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user