Dynamic CSV load in SQL Server - sql-server

Ok so I have been searching the internet for a solution but have not yet come up with anything yet
What I have is a CSV - this CSV could have any number of unknown columns
e.g.
Col 1, Col 2, Col 3
I have used BULK INSERT #temp FROM ... to insert from a CSV but this relies on me having a table before hand to load into - This is where the problem arises - I don’t know my table structure before loading the CSV
Is there a way to dynamically create the table, based on the CSV, on the fly to load the data into?
Thanks
Rob

I was faced with the same tasks many many times. What I ended up doing is writing a simple c# script for the load. I can admit, each time I had to change the script a little bit, because each time the requirements were different, the CSV file had specific peculiarities, etc. This means that my code most likely won't work for you straight away, but I hope that it can help you a lot.
The main C# file is program.cs. Here is its source:
using System;
using System.IO;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using System.Collections;
namespace CsvToSql
{
class Program
{
static string server = #"localhost";
static string database = #"test";
static bool hasHeaders = false;
static string fieldLength = "max";
static string fieldPattern = "[%fieldName%] [nvarchar](%fieldLength%) NULL,\n";
static string createTablePattern =
#"
IF EXISTS (SELECT * FROM sys.objects WHERE object_id = OBJECT_ID(N'[dbo].[%tableName%]') AND type in (N'U'))
DROP TABLE [dbo].[%tableName%]
GO
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
CREATE TABLE [dbo].[%tableName%](
%fields%
) ON [PRIMARY]
";
static string commandScriptPattern =
#"sqlcmd -S %server% -E -d %database% -i %tableScriptName%
bcp %database%.dbo.%tableName% in %headrelsessFileName% -c -t^^ -r \n -T -S %server%
";
private static void Main(string[] args)
{
server = System.Configuration.ConfigurationSettings.AppSettings["server"] ?? server;
database = System.Configuration.ConfigurationSettings.AppSettings["database"] ?? database;
hasHeaders = System.Configuration.ConfigurationSettings.AppSettings["hasHeaders"] == "true";
fieldLength = System.Configuration.ConfigurationSettings.AppSettings["fieldLength"] ?? fieldLength;
string[] fileNames = Directory.GetFiles(".", "*.csv");
foreach (string fileName in fileNames)
{
Console.WriteLine("Processing {0}", fileName);
Process(fileName);
}
WriteExecuteAllFile(fileNames);
WriteCleanUpFile(fileNames);
}
private static void Process(string fileName)
{
string[] fieldNames = ReadHeaders(fileName);
ProduceTableScript(fileName, fieldNames);
ProduceCommandScript(fileName);
}
private static void WriteExecuteAllFile(string[] fileNames)
{
StringBuilder sb = new StringBuilder();
foreach (string fileName in fileNames)
{
sb.Append("call ");
sb.AppendLine(GetCommandScriptName(fileName));
}
SaveStringToFile(sb.ToString(), "_all.cmd");
}
private static void WriteCleanUpFile(string[] fileNames)
{
StringBuilder sb = new StringBuilder();
foreach (string fileName in fileNames)
{
sb.Append("del ");
sb.AppendLine(GetCommandScriptName(fileName));
sb.Append("del ");
sb.AppendLine(GetHeaderlessFileName(fileName));
sb.Append("del ");
sb.AppendLine(GetTableScriptName(fileName));
}
sb.AppendLine("del _all.cmd");
sb.AppendLine("del _cleanup.cmd");
SaveStringToFile(sb.ToString(), "_cleanup.cmd");
}
private static string[] ReadHeaders(string fileName)
{
using (FileStream fs = File.OpenRead(fileName))
using (StreamReader sr = new StreamReader(fs))
{
if (hasHeaders)
{
string[] result = ParseQutationLineToList(sr.ReadLine());
ProduceHeaderlessFile(sr, fs.Name);
return result;
}
else
{
string s = sr.ReadLine();
string[] fields = ParseQutationLineToList(s);
fs.Seek(0, SeekOrigin.Begin);
sr.DiscardBufferedData();
string[] result = new string[fields.Length];
for (int i = 0; i < fields.Length; i++)
{
result[i] = "F" + (i + 1).ToString();
}
ProduceHeaderlessFile(sr, fs.Name);
return result;
}
}
}
private static void ProduceTableScript(string fileName, string[] fieldNames)
{
string tableName = GetTableName(fileName);
string fields = fieldNames.Aggregate("", (s, i) => s + fieldPattern.Replace("%fieldName%", i).Replace("%fieldLength%", fieldLength));
string table = createTablePattern.Replace("%fields%", fields).Replace("%tableName%", tableName);
SaveStringToFile(table, GetTableScriptName(fileName));
}
private static void ProduceCommandScript(string fileName)
{
string content = commandScriptPattern;
content = content.Replace("%server%", server);
content = content.Replace("%database%", database);
content = content.Replace("%tableName%", GetTableName(fileName));
content = content.Replace("%tableScriptName%", GetTableScriptName(fileName));
content = content.Replace("%headrelsessFileName%", GetHeaderlessFileName(fileName));
SaveStringToFile(content, GetCommandScriptName(fileName));
}
private static void ProduceHeaderlessFile(StreamReader sr, string basefileName)
{
string headerlessFileName = GetHeaderlessFileName(basefileName);
if (File.Exists(headerlessFileName))
{
return;
}
int counter = 0;
using(FileStream fs = File.Open(headerlessFileName, FileMode.Create, FileAccess.Write, FileShare.Read))
using (StreamWriter sw = new StreamWriter(fs))
{
while(!sr.EndOfStream)
{
//sw.WriteLine(sr.ReadLine().Replace("\"", ""));
sw.WriteLine(ParseLine(sr.ReadLine()));
counter++;
}
sw.Flush();
fs.Flush();
}
Console.WriteLine("Written {0} records to {1}", counter, headerlessFileName);
}
private static string ParseLine(string s)
{
if (s.TrimStart(' ', '\t').StartsWith("\""))
{
return ParseQutationLine(s);
}
return s.Replace(',', '^');
}
// Some tables has the default field terminator (comma) inside them
// this is why we have to parse
private static string ParseQutationLine(string s)
{
string[] fields = ParseQutationLineToList(s);
StringBuilder sb = new StringBuilder();
foreach (string field in fields)
{
sb.Append(field.Trim('"'));
sb.Append('^');
if (field.IndexOf('^') >= 0)
{
throw new ApplicationException("String contains separator character. " + s);
}
}
return sb.ToString().Substring(0, sb.Length - 1);
}
private static string[] ParseQutationLineToList(string s)
{
JouniHeikniemi.Tools.Strings.CsvReader cr = new JouniHeikniemi.Tools.Strings.CsvReader();
ArrayList result = new ArrayList();
cr.ParseCsvFields(result, s);
return (string[])result.ToArray(typeof(string));
}
private static void SaveStringToFile(string s, string fileName)
{
using (FileStream fs = File.Open(fileName, FileMode.Create, FileAccess.Write, FileShare.Read))
using (StreamWriter sw = new StreamWriter(fs))
{
sw.Write(s);
sw.Flush();
fs.Flush();
}
}
private static string GetTableName(string fileName)
{
return "_" + Path.GetFileNameWithoutExtension(fileName).Replace('.', '_');
}
private static string GetHeaderlessFileName(string fileName)
{
return Path.ChangeExtension(fileName, "inp");
}
private static string GetTableScriptName(string fileName)
{
return Path.ChangeExtension(fileName, "tbl");
}
private static string GetCommandScriptName(string fileName)
{
return Path.ChangeExtension(fileName, "cmd");
}
}
}
This file use a library that I found in internet for parsing CSV file. Note, that I saw valid CSV files, that this library failed to parse. The text for CsvReader.cs file follows:
using System;
using System.Collections;
using System.IO;
using System.Text;
namespace JouniHeikniemi.Tools.Strings {
/// <summary>
/// A data-reader style interface for reading Csv (and otherwise-char-separated) files.
/// </summary>
public class CsvReader : IDisposable {
#region Private variables
private Stream stream;
private StreamReader reader;
private char separator;
#endregion
#region Constructors
public CsvReader() { separator = ','; }
/// <summary>
/// Creates a new Csv reader for the given stream.
/// </summary>
/// <param name="s">The stream to read the CSV from.</param>
public CsvReader(Stream s) : this(s, null, ',') { }
/// <summary>
/// Creates a new reader for the given stream and separator.
/// </summary>
/// <param name="s">The stream to read the separator from.</param>
/// <param name="separator">The field separator character</param>
public CsvReader(Stream s, char separator) : this(s, null, separator) { }
/// <summary>
/// Creates a new Csv reader for the given stream and encoding.
/// </summary>
/// <param name="s">The stream to read the CSV from.</param>
/// <param name="enc">The encoding used.</param>
public CsvReader(Stream s, Encoding enc) : this(s, enc, ',') { }
/// <summary>
/// Creates a new reader for the given stream, encoding and separator character.
/// </summary>
/// <param name="s">The stream to read the data from.</param>
/// <param name="enc">The encoding used.</param>
/// <param name="separator">The separator character between the fields</param>
public CsvReader(Stream s, Encoding enc, char separator) {
this.separator = separator;
this.stream = s;
if (!s.CanRead) {
throw new CsvReaderException("Could not read the given data stream!");
}
reader = (enc != null) ? new StreamReader(s, enc) : new StreamReader(s);
}
/// <summary>
/// Creates a new Csv reader for the given text file path.
/// </summary>
/// <param name="filename">The name of the file to be read.</param>
public CsvReader(string filename) : this(filename, null, ',') { }
/// <summary>
/// Creates a new reader for the given text file path and separator character.
/// </summary>
/// <param name="filename">The name of the file to be read.</param>
/// <param name="separator">The field separator character</param>
public CsvReader(string filename, char separator) : this(filename, null, separator) { }
/// <summary>
/// Creates a new Csv reader for the given text file path and encoding.
/// </summary>
/// <param name="filename">The name of the file to be read.</param>
/// <param name="enc">The encoding used.</param>
public CsvReader(string filename, Encoding enc)
: this(filename, enc, ',') { }
/// <summary>
/// Creates a new reader for the given text file path, encoding and field separator.
/// </summary>
/// <param name="filename">The name of the file to be read.</param>
/// <param name="enc">The encoding used.</param>
/// <param name="separator">The field separator character.</param>
public CsvReader(string filename, Encoding enc, char separator)
: this(new FileStream(filename, FileMode.Open), enc, separator) { }
#endregion
#region Properties
/// <summary>
/// The separator character for the fields. Comma for normal CSV.
/// </summary>
public char Separator {
get { return separator; }
set { separator = value; }
}
#endregion
#region Parsing
/// <summary>
/// Returns the fields for the next row of data (or null if at eof)
/// </summary>
/// <returns>A string array of fields or null if at the end of file.</returns>
public string[] GetCsvLine() {
string data = reader.ReadLine();
if (data == null) return null;
if (data.Length == 0) return new string[0];
ArrayList result = new ArrayList();
ParseCsvFields(result, data);
return (string[])result.ToArray(typeof(string));
}
// Parses the fields and pushes the fields into the result arraylist
public void ParseCsvFields(ArrayList result, string data) {
int pos = -1;
while (pos < data.Length)
result.Add(ParseCsvField(data, ref pos));
}
// Parses the field at the given position of the data, modified pos to match
// the first unparsed position and returns the parsed field
private string ParseCsvField(string data, ref int startSeparatorPosition) {
if (startSeparatorPosition == data.Length-1) {
startSeparatorPosition++;
// The last field is empty
return "";
}
int fromPos = startSeparatorPosition + 1;
// Determine if this is a quoted field
if (data[fromPos] == '"') {
// If we're at the end of the string, let's consider this a field that
// only contains the quote
if (fromPos == data.Length-1) {
fromPos++;
return "\"";
}
// Otherwise, return a string of appropriate length with double quotes collapsed
// Note that FSQ returns data.Length if no single quote was found
int nextSingleQuote = FindSingleQuote(data, fromPos+1);
startSeparatorPosition = nextSingleQuote+1;
return data.Substring(fromPos+1, nextSingleQuote-fromPos-1).Replace("\"\"", "\"");
}
// The field ends in the next separator or EOL
int nextSeparator = data.IndexOf(separator, fromPos);
if (nextSeparator == -1) {
startSeparatorPosition = data.Length;
return data.Substring(fromPos);
}
else {
startSeparatorPosition = nextSeparator;
return data.Substring(fromPos, nextSeparator - fromPos);
}
}
// Returns the index of the next single quote mark in the string
// (starting from startFrom)
private static int FindSingleQuote(string data, int startFrom) {
int i = startFrom-1;
while (++i < data.Length)
if (data[i] == '"') {
// If this is a double quote, bypass the chars
if (i < data.Length-1 && data[i+1] == '"') {
i++;
continue;
}
else
return i;
}
// If no quote found, return the end value of i (data.Length)
return i;
}
#endregion
/// <summary>
/// Disposes the reader. The underlying stream is closed.
/// </summary>
public void Dispose() {
// Closing the reader closes the underlying stream, too
if (reader != null) reader.Close();
else if (stream != null)
stream.Close(); // In case we failed before the reader was constructed
GC.SuppressFinalize(this);
}
}
/// <summary>
/// Exception class for CsvReader exceptions.
/// </summary>
[Serializable]
public class CsvReaderException : ApplicationException {
/// <summary>
/// Constructs a new CsvReaderException.
/// </summary>
public CsvReaderException() : this("The CSV Reader encountered an error.") { }
/// <summary>
/// Constructs a new exception with the given message.
/// </summary>
/// <param name="message">The exception message.</param>
public CsvReaderException(string message) : base(message) { }
/// <summary>
/// Constructs a new exception with the given message and the inner exception.
/// </summary>
/// <param name="message">The exception message.</param>
/// <param name="inner">Inner exception that caused this issue.</param>
public CsvReaderException(string message, Exception inner) : base(message, inner) { }
/// <summary>
/// Constructs a new exception with the given serialization information.
/// </summary>
/// <param name="info"></param>
/// <param name="context"></param>
protected CsvReaderException(System.Runtime.Serialization.SerializationInfo info,
System.Runtime.Serialization.StreamingContext context)
: base(info, context) { }
}
}
I also have a config file CsvToSql.exe.config:
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<appSettings>
<add key="server" value="localhost"/>
<add key="database" value="test"/>
<!-- If your csv files have header, set this to true to generate field names from headers-->
<!-- Otherwise set it to false to generate names F1, F2, F3, etc.-->
<add key="hasHeaders" value="false"/>
<!-- This is the lenght of nvarchar field created can be a number or 'max'-->
<add key="fieldLength" value="500"/>
</appSettings>
</configuration>
And a script that compiles the whole lot build.cmd:
%systemroot%\Microsoft.NET\Framework\v3.5\csc.exe /out:CsvToSql.exe Program.cs CsvReader.cs
This is how I use it:
Run build.cmd to compile CsvToSql.exe
Edit CsvToSql.exe.config to fit your case
Put csv files in the same folder the executable and the config file
Run CsvToSql.exe
The executable does not connect to the database. Instead it produces a number of files:
*.tbl files are table definitions, *.inp files are input files for bcp command line utility, *.cmd files are files that run table creation scripts and bcp command line utility. _all.cmd that runs *.cmd for all tables and _cleanup.cmd that deletes all the files that CsvToSql.exe generates
Run _all.cmd file
Go to your SQL and look at what has been produced. Make changes to the script and / or config, rinse and repeat
There are a lot of assumtions that this script makes, and also a lot of stuff that is hardcoded. This is what I usaully quickly change each new time I need to load a set of CSV into SQL.
Good luck and if you have any questions please don't hesitate to ask.
The script requires .NET 3.5
If there is no extra-special about data I'm loading, I'm usually up and running with this script in 15 minutes. If there are troubles, twicking might take longer.

CSV parsing is non-trivial (taking into account text qualifiers, values that contain linebreaks, qualifier escape mechanisms, etc). There are several .Net libraries out there that do all this stuff for you (eg http://www.codeproject.com/KB/database/CsvReader.aspx), so I would think it would be easier to use a different technology, eg powershell, or SQL CLR, to make use of an existing library - rather that trying to roll your own CSV parser in T-SQL...
Huh, just found this nice and simple solution on an old forum post (http://forums.databasejournal.com/showthread.php?t=47966):
select *
from OpenRowset('MSDASQL', 'Driver={Microsoft Text Driver (*.txt; *.csv)}; DefaultDir=D:\;',
'select * from test1.csv')
Unfortunately, it doesn't work on recent windows versions where the text driver isn't installed by default...

Related

I am having trouble locating an issue with my xml files how do I locate a Bad signature (0x01549FF9) at position 0x001D4DA8

While attempting to troubleshoot a use case I get a "Bad signature (0x01549FF9) at position 0x001D4DA8" exception after trying to open the workbook. What is the best way to find what part of the xlsx xml is causing this issue?
Opening the file in Excel and saving it resolves the issue and I can see from diffs that Excel must have found and corrected "errors" but there are too many to definitively figure out which one is the issue.
[TestMethod]
public void FileSaving_FileOpensAfterMultipleEdits_FunctioningFile()
{
var template = #"C:\xxxxx\test.xlsx";
var outputSmokeTest = Path.Combine(testDocumentsRootDirectory, #"Results\CorruptTest.xlsx");
smokeTestWorkbook = new Workbook(key);
if (File.Exists(outputSmokeTest))
File.Delete(outputSmokeTest);
smokeTestWorkbook.Open(template);
smokeTestWorkbook.SaveAs(outputSmokeTest);
}
/// <summary>
/// Opens a reference to the workbook
/// </summary>
/// <returns></returns>
public void Open(string workbookPath)
{
var loggerString = "Open";
if (File.Exists(workbookPath))
{
try
{
logger.Track(loggerString);
excelPackage = new ExcelPackage(new FileInfo(workbookPath));
}
catch (Exception ex)
{
logger.TrackException(loggerString, ex);
}
}
}
the new ExcelPackage line is what causes the exception.

How to suppress launching beyond compare 3 while doing a comparison from windows forms application

I am using Beyond Compare 3 in my win forms application to do a comparison in two output folders (ProdOutput and SITOutput). I am using the below lines of code for doing the comparison
public static void LaunchViewer(string filepath1, string filepath2)
{
string arguments = String.Format("\"{0}\" \"{1}\"", filepath1, filepath2);
ProcessStartInfo psi = new ProcessStartInfo(ApplicationPath, arguments);
using (Process p = Process.Start(psi))
{
ComparsionResult = CompareFiles(filepath1, filepath2, BeyondCompareRules.EverythingElse);
}
}
public static ComparisonResult CompareFiles(string filepath1, string filepath2, string ruleName)
{
ComparisonResult result = ComparisonResult.None;
string arguments = String.Format("/quickcompare /rules=\"{0}\" \"{1}\" \"{2}\"", ruleName, filepath1, filepath2);
ProcessStartInfo psi = new ProcessStartInfo(ApplicationPath, arguments);
psi.UseShellExecute = false;
psi.RedirectStandardInput = true;
psi.RedirectStandardOutput = true;
using (Process p = Process.Start(psi))
{
p.StandardInput.WriteLine("EXIT [ErrorLevel]");
p.WaitForExit();
int exitCode = p.ExitCode;
switch (exitCode)
{
case 0:
result = ComparisonResult.Match;
break;
case 1:
result = ComparisonResult.Similar;
break;
case 2:
result = ComparisonResult.DoNotMatch;
break;
case 3:
result = ComparisonResult.ComparisonError;
break;
default :
result = ComparisonResult.DoNotMatch;
break;
}
}
return result;
}
Beyond compare rules are below
public sealed class BeyondCompareRules
{
private BeyondCompareRules()
{
}
/// <summary>
/// A comparison rule set for C/C++/C# source files
/// </summary>
public const string CLanguageSource = "C/C++/C# Source";
public const string Cobol = "COBOL";
public const string CommaSeparatedValues = "Comma Separated Values";
public const string DelphiSource = "Delphi Source";
public const string DelphiForms = "Delphi Forms";
public const string GeneralText = "General Text";
public const string Html = "HTML";
public const string Java = "JAVA";
public const string Python = "Python";
public const string RegistryDump = "Registry Dump";
public const string Utf8Text = "UTF8 Text";
public const string VisualBasic = "Visual Basic";
public const string Xml = "XML";
/// <summary>
/// The default set of comparison rules
/// </summary>
public const string EverythingElse = "Everything Else";
}
and comparisonresult is an enum as below
public enum ComparisonResult
{
/// <summary>
/// Indicates a null or uninitialized value
/// </summary>
None = 0,
/// <summary>
/// The Quick Compare returned a Positive Match
/// </summary>
Match = 1,
/// <summary>
/// The Quick Compare detected small differences
/// </summary>
Similar = 2,
/// <summary>
/// The Quick Compare detected significant differences
/// </summary>
DoNotMatch = 3,
/// <summary>
/// The Quick Compare utility returned an error/unknown result
/// </summary>
ComparisonError = 4
}
What I need is to suppress launching the beyond compare screen when doing the comparison but the comparison should happen and it should return result. Now with my above code I am able to do the comparison and also able to view the differences which I don't want to do.
I guess there is some thing which I can do with passing arguments but not sure what is it and how and where should I put it.
Any help highly appreciated.
After several trials with different ways to achieve the functionality, finally a small piece of code did resolved the issue.
There is a command line switches available for beyond compare application which needs to be passed when we want to suppress the interactions. It is called "/silent".
I have passed along with these lines in CompareFiles method
public static ComparisonResult CompareFiles(string filepath1, string filepath2, string ruleName)
ComparisonResult result = ComparisonResult.None;
string arguments = String.Format("/quickcompare /rules=\"{0}\" \"{1}\" \"{2}\" /silent", ruleName, filepath1, filepath2);
All the above posted code in my question is working fine and can be used by any one who wants to suppress the interactions.

Themes and Languages Resource Dictionary

I was using the standard approach to themes in my WPF application and using a dynamicresource to change it during runtime.
This worked.
Now I have added language support (english, spanish) but when this is set, the dynamic resource no longer works for the theme. If I set the control to a static resource the theme works but then I lose the affect of changing it during runtime.
Heres the code that was added for language support but I'm confused as to why it stopped working the way I want it to.
App.xaml.cs calls this:
SetLanguage(LocalePath(CultureInfo.CurrentCulture.Name));
/// <summary>
/// Get the locale path for the resource dictionary.
/// </summary>
/// <param name="language"></param>
/// <returns></returns>
private string LocalePath(string language)
{
string file = language + ".xaml";
return Path.Combine(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location), #"Utilities\Resources\Locales\", file);
}
/// <summary>
/// Set the language using the defined resource file.
/// </summary>
/// <param name="file"></param>
private void SetLanguage(string file)
{
if (File.Exists(file))
{
var languageDictionary = new ResourceDictionary();
languageDictionary.Source = new Uri(file);
int dictionaryID = -1;
for (int i = 0; i < Resources.MergedDictionaries.Count; i++)
{
var mergedDictionary = Resources.MergedDictionaries[i];
if (mergedDictionary.Contains("ResourceDictionaryName"))
{
if (mergedDictionary["ResourceDictionaryName"].ToString().StartsWith("Locale-"))
{
dictionaryID = i;
break;
}
}
}
if (dictionaryID == -1)
{
Resources.MergedDictionaries.Add(languageDictionary);
}
else
{
Resources.MergedDictionaries[dictionaryID] = languageDictionary;
}
}
}
I had 5 other keys in the language resource dictionaries with the same key as the theme being used. Changed the key name and it works again.

Validation in textbox in WPF

I am currently working on a WPF application where I would like to have a TextBox that can only have numeric entries in it. I know that I can validate the content of it when I lost the focus and block the content from being numeric, but in other Windows Form application, we use to totally block any input except numerical from being written down. Plus, we use to put that code in a separate dll to reference it in many places.
Here is the code in 2008 not using WPF:
Public Shared Sub BloquerInt(ByRef e As System.Windows.Forms.KeyPressEventArgs, ByRef oTxt As Windows.Forms.TextBox, ByVal intlongueur As Integer)
Dim intLongueurSelect As Integer = oTxt.SelectionLength
Dim intPosCurseur As Integer = oTxt.SelectionStart
Dim strValeurTxtBox As String = oTxt.Text.Substring(0, intPosCurseur) & oTxt.Text.Substring(intPosCurseur + intLongueurSelect, oTxt.Text.Length - intPosCurseur - intLongueurSelect)
If IsNumeric(e.KeyChar) OrElse _
Microsoft.VisualBasic.Asc(e.KeyChar) = System.Windows.Forms.Keys.Back Then
If Microsoft.VisualBasic.AscW(e.KeyChar) = System.Windows.Forms.Keys.Back Then
e.Handled = False
ElseIf strValeurTxtBox.Length < intlongueur Then
e.Handled = False
Else
e.Handled = True
End If
Else
e.Handled = True
End If
Is there an equivalent way in WPF? I wouldn't mind if this is in a style but I am new to WPF so style are a bit obscure to what they can or can't do.
You can restrict the input to numbers only using an attached property on the TextBox. Define the attached property once (even in a separate dll) and use it on any TextBox. Here is the attached property:
using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
/// <summary>
/// Class that provides the TextBox attached property
/// </summary>
public static class TextBoxService
{
/// <summary>
/// TextBox Attached Dependency Property
/// </summary>
public static readonly DependencyProperty IsNumericOnlyProperty = DependencyProperty.RegisterAttached(
"IsNumericOnly",
typeof(bool),
typeof(TextBoxService),
new UIPropertyMetadata(false, OnIsNumericOnlyChanged));
/// <summary>
/// Gets the IsNumericOnly property. This dependency property indicates the text box only allows numeric or not.
/// </summary>
/// <param name="d"><see cref="DependencyObject"/> to get the property from</param>
/// <returns>The value of the StatusBarContent property</returns>
public static bool GetIsNumericOnly(DependencyObject d)
{
return (bool)d.GetValue(IsNumericOnlyProperty);
}
/// <summary>
/// Sets the IsNumericOnly property. This dependency property indicates the text box only allows numeric or not.
/// </summary>
/// <param name="d"><see cref="DependencyObject"/> to set the property on</param>
/// <param name="value">value of the property</param>
public static void SetIsNumericOnly(DependencyObject d, bool value)
{
d.SetValue(IsNumericOnlyProperty, value);
}
/// <summary>
/// Handles changes to the IsNumericOnly property.
/// </summary>
/// <param name="d"><see cref="DependencyObject"/> that fired the event</param>
/// <param name="e">A <see cref="DependencyPropertyChangedEventArgs"/> that contains the event data.</param>
private static void OnIsNumericOnlyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
bool isNumericOnly = (bool)e.NewValue;
TextBox textBox = (TextBox)d;
if (isNumericOnly)
{
textBox.PreviewTextInput += BlockNonDigitCharacters;
textBox.PreviewKeyDown += ReviewKeyDown;
}
else
{
textBox.PreviewTextInput -= BlockNonDigitCharacters;
textBox.PreviewKeyDown -= ReviewKeyDown;
}
}
/// <summary>
/// Disallows non-digit character.
/// </summary>
/// <param name="sender">The source of the event.</param>
/// <param name="e">An <see cref="TextCompositionEventArgs"/> that contains the event data.</param>
private static void BlockNonDigitCharacters(object sender, TextCompositionEventArgs e)
{
foreach (char ch in e.Text)
{
if (!Char.IsDigit(ch))
{
e.Handled = true;
}
}
}
/// <summary>
/// Disallows a space key.
/// </summary>
/// <param name="sender">The source of the event.</param>
/// <param name="e">An <see cref="KeyEventArgs"/> that contains the event data.</param>
private static void ReviewKeyDown(object sender, KeyEventArgs e)
{
if (e.Key == Key.Space)
{
// Disallow the space key, which doesn't raise a PreviewTextInput event.
e.Handled = true;
}
}
}
Here is how to use it (replace "controls" with your own namespace):
<TextBox controls:TextBoxService.IsNumericOnly="True" />
You can put a validation in your binding
<TextBox>
<TextBox.Text>
<Binding Path="CategoriaSeleccionada.ColorFondo"
UpdateSourceTrigger="PropertyChanged">
<Binding.ValidationRules>
<utilities:RGBValidationRule />
</Binding.ValidationRules>
</Binding>
</TextBox.Text>
</TextBox>
Look at this example (of my program), you put the validation inside the binding like this. With UpdateSourceTrigger you can change when you binding will be updated (lost focus, in every change...)
Well, the validation is a class, I will put you an example:
class RGBValidationRule : ValidationRule
{
public override ValidationResult Validate(object value, System.Globalization.CultureInfo cultureInfo)
{
// Here you make your validation using the value object.
// If you want to check if the object is only numbers you can
// Use some built-in method
string blah = value.ToString();
int num;
bool isNum = int.TryParse(blah, out num);
if (isNum) return new ValidationResult(true, null);
else return new ValidationResult(false, "It's no a number");
}
}
In short, do the job inside that method and return a new ValidationResult. The first parameter is a bool, true if the validation is good, false if not. The second parameter is only a message for information.
I think that this is the basics of textbox validation.
Hope this help.
EDIT: Sorry, I don't know VB.NET but I think that the C# code is pretty simple.

Formatting text in WinForm Label

Is it possible to format certain text in a WinForm Label instead of breaking the text into multiple labels? Please disregard the HTML tags within the label's text; it's only used to get my point out.
For example:
Dim myLabel As New Label
myLabel.Text = "This is <b>bold</b> text. This is <i>italicized</i> text."
Which would produce the text in the label as:
This is bold text. This is
italicized text.
That's not possible with a WinForms label as it is. The label has to have exactly one font, with exactly one size and one face. You have a couple of options:
Use separate labels
Create a new Control-derived class that does its own drawing via GDI+ and use that instead of Label; this is probably your best option, as it gives you complete control over how to instruct the control to format its text
Use a third-party label control that will let you insert HTML snippets (there are a bunch - check CodeProject); this would be someone else's implementation of #2.
Not really, but you could fake it with a read-only RichTextBox without borders. RichTextBox supports Rich Text Format (rtf).
Another workaround, late to the party: if you don't want to use a third party control, and you're just looking to call attention to some of the text in your label, and you're ok with underlines, you can use a LinkLabel.
Note that many consider this a 'usability crime', but if you're not designing something for end user consumption then it may be something you're prepared to have on your conscience.
The trick is to add disabled links to the parts of your text that you want underlined, and then globally set the link colors to match the rest of the label. You can set almost all the necessary properties at design-time apart from the Links.Add() piece, but here they are in code:
linkLabel1.Text = "You are accessing a government system, and all activity " +
"will be logged. If you do not wish to continue, log out now.";
linkLabel1.AutoSize = false;
linkLabel1.Size = new Size(365, 50);
linkLabel1.TextAlign = ContentAlignment.MiddleCenter;
linkLabel1.Links.Clear();
linkLabel1.Links.Add(20, 17).Enabled = false; // "government system"
linkLabel1.Links.Add(105, 11).Enabled = false; // "log out now"
linkLabel1.LinkColor = linkLabel1.ForeColor;
linkLabel1.DisabledLinkColor = linkLabel1.ForeColor;
Result:
Worked solution for me - using custom RichEditBox. With right properties it will be looked as simple label with bold support.
1) First, add your custom RichTextLabel class with disabled caret :
public class RichTextLabel : RichTextBox
{
public RichTextLabel()
{
base.ReadOnly = true;
base.BorderStyle = BorderStyle.None;
base.TabStop = false;
base.SetStyle(ControlStyles.Selectable, false);
base.SetStyle(ControlStyles.UserMouse, true);
base.SetStyle(ControlStyles.SupportsTransparentBackColor, true);
base.MouseEnter += delegate(object sender, EventArgs e)
{
this.Cursor = Cursors.Default;
};
}
protected override void WndProc(ref Message m)
{
if (m.Msg == 0x204) return; // WM_RBUTTONDOWN
if (m.Msg == 0x205) return; // WM_RBUTTONUP
base.WndProc(ref m);
}
}
2) Split you sentence to words with IsSelected flag, that determine if that word should be bold or no :
private void AutocompleteItemControl_Load(object sender, EventArgs e)
{
RichTextLabel rtl = new RichTextLabel();
rtl.Font = new Font("MS Reference Sans Serif", 15.57F);
StringBuilder sb = new StringBuilder();
sb.Append(#"{\rtf1\ansi ");
foreach (var wordPart in wordParts)
{
if (wordPart.IsSelected)
{
sb.Append(#"\b ");
}
sb.Append(ConvertString2RTF(wordPart.WordPart));
if (wordPart.IsSelected)
{
sb.Append(#"\b0 ");
}
}
sb.Append(#"}");
rtl.Rtf = sb.ToString();
rtl.Width = this.Width;
this.Controls.Add(rtl);
}
3) Add function for convert you text to valid rtf (with unicode support!) :
private string ConvertString2RTF(string input)
{
//first take care of special RTF chars
StringBuilder backslashed = new StringBuilder(input);
backslashed.Replace(#"\", #"\\");
backslashed.Replace(#"{", #"\{");
backslashed.Replace(#"}", #"\}");
//then convert the string char by char
StringBuilder sb = new StringBuilder();
foreach (char character in backslashed.ToString())
{
if (character <= 0x7f)
sb.Append(character);
else
sb.Append("\\u" + Convert.ToUInt32(character) + "?");
}
return sb.ToString();
}
Works like a charm for me!
Solutions compiled from :
How to convert a string to RTF in C#?
Format text in Rich Text Box
How to hide the caret in a RichTextBox?
Create the text as a RTF file in wordpad
Create Rich text control with no borders and editable = false
Add the RTF file to the project as a resource
In the Form1_load do
myRtfControl.Rtf = Resource1.MyRtfControlText
AutoRichLabel
I was solving this problem by building an UserControl that contains a TransparentRichTextBox that is readonly. The TransparentRichTextBox is a RichTextBox that allows to be transparent:
TransparentRichTextBox.cs:
public class TransparentRichTextBox : RichTextBox
{
[DllImport("kernel32.dll", CharSet = CharSet.Auto)]
static extern IntPtr LoadLibrary(string lpFileName);
protected override CreateParams CreateParams
{
get
{
CreateParams prams = base.CreateParams;
if (TransparentRichTextBox.LoadLibrary("msftedit.dll") != IntPtr.Zero)
{
prams.ExStyle |= 0x020; // transparent
prams.ClassName = "RICHEDIT50W";
}
return prams;
}
}
}
The final UserControl acts as wrapper of the TransparentRichTextBox. Unfortunately, I had to limit it to AutoSize on my own way, because the AutoSize of the RichTextBox became broken.
AutoRichLabel.designer.cs:
partial class AutoRichLabel
{
/// <summary>
/// Required designer variable.
/// </summary>
private System.ComponentModel.IContainer components = null;
/// <summary>
/// Clean up any resources being used.
/// </summary>
/// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
protected override void Dispose(bool disposing)
{
if (disposing && (components != null))
{
components.Dispose();
}
base.Dispose(disposing);
}
#region Component Designer generated code
/// <summary>
/// Required method for Designer support - do not modify
/// the contents of this method with the code editor.
/// </summary>
private void InitializeComponent()
{
this.rtb = new TransparentRichTextBox();
this.SuspendLayout();
//
// rtb
//
this.rtb.BorderStyle = System.Windows.Forms.BorderStyle.None;
this.rtb.Dock = System.Windows.Forms.DockStyle.Fill;
this.rtb.Location = new System.Drawing.Point(0, 0);
this.rtb.Margin = new System.Windows.Forms.Padding(0);
this.rtb.Name = "rtb";
this.rtb.ReadOnly = true;
this.rtb.ScrollBars = System.Windows.Forms.RichTextBoxScrollBars.None;
this.rtb.Size = new System.Drawing.Size(46, 30);
this.rtb.TabIndex = 0;
this.rtb.Text = "";
this.rtb.WordWrap = false;
this.rtb.ContentsResized += new System.Windows.Forms.ContentsResizedEventHandler(this.rtb_ContentsResized);
//
// AutoRichLabel
//
this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
this.AutoSizeMode = System.Windows.Forms.AutoSizeMode.GrowAndShrink;
this.BackColor = System.Drawing.Color.Transparent;
this.Controls.Add(this.rtb);
this.Name = "AutoRichLabel";
this.Size = new System.Drawing.Size(46, 30);
this.ResumeLayout(false);
}
#endregion
private TransparentRichTextBox rtb;
}
AutoRichLabel.cs:
/// <summary>
/// <para>An auto sized label with the ability to display text with formattings by using the Rich Text Format.</para>
/// <para>­</para>
/// <para>Short RTF syntax examples: </para>
/// <para>­</para>
/// <para>Paragraph: </para>
/// <para>{\pard This is a paragraph!\par}</para>
/// <para>­</para>
/// <para>Bold / Italic / Underline: </para>
/// <para>\b bold text\b0</para>
/// <para>\i italic text\i0</para>
/// <para>\ul underline text\ul0</para>
/// <para>­</para>
/// <para>Alternate color using color table: </para>
/// <para>{\colortbl ;\red0\green77\blue187;}{\pard The word \cf1 fish\cf0 is blue.\par</para>
/// <para>­</para>
/// <para>Additional information: </para>
/// <para>Always wrap every text in a paragraph. </para>
/// <para>Different tags can be stacked (i.e. \pard\b\i Bold and Italic\i0\b0\par)</para>
/// <para>The space behind a tag is ignored. So if you need a space behind it, insert two spaces (i.e. \pard The word \bBOLD\0 is bold.\par)</para>
/// <para>Full specification: http://www.biblioscape.com/rtf15_spec.htm </para>
/// </summary>
public partial class AutoRichLabel : UserControl
{
/// <summary>
/// The rich text content.
/// <para>­</para>
/// <para>Short RTF syntax examples: </para>
/// <para>­</para>
/// <para>Paragraph: </para>
/// <para>{\pard This is a paragraph!\par}</para>
/// <para>­</para>
/// <para>Bold / Italic / Underline: </para>
/// <para>\b bold text\b0</para>
/// <para>\i italic text\i0</para>
/// <para>\ul underline text\ul0</para>
/// <para>­</para>
/// <para>Alternate color using color table: </para>
/// <para>{\colortbl ;\red0\green77\blue187;}{\pard The word \cf1 fish\cf0 is blue.\par</para>
/// <para>­</para>
/// <para>Additional information: </para>
/// <para>Always wrap every text in a paragraph. </para>
/// <para>Different tags can be stacked (i.e. \pard\b\i Bold and Italic\i0\b0\par)</para>
/// <para>The space behind a tag is ignored. So if you need a space behind it, insert two spaces (i.e. \pard The word \bBOLD\0 is bold.\par)</para>
/// <para>Full specification: http://www.biblioscape.com/rtf15_spec.htm </para>
/// </summary>
[Browsable(true)]
public string RtfContent
{
get
{
return this.rtb.Rtf;
}
set
{
this.rtb.WordWrap = false; // to prevent any display bugs, word wrap must be off while changing the rich text content.
this.rtb.Rtf = value.StartsWith(#"{\rtf1") ? value : #"{\rtf1" + value + "}"; // Setting the rich text content will trigger the ContentsResized event.
this.Fit(); // Override width and height.
this.rtb.WordWrap = this.WordWrap; // Set the word wrap back.
}
}
/// <summary>
/// Dynamic width of the control.
/// </summary>
[Browsable(false)]
public new int Width
{
get
{
return base.Width;
}
}
/// <summary>
/// Dynamic height of the control.
/// </summary>
[Browsable(false)]
public new int Height
{
get
{
return base.Height;
}
}
/// <summary>
/// The measured width based on the content.
/// </summary>
public int DesiredWidth { get; private set; }
/// <summary>
/// The measured height based on the content.
/// </summary>
public int DesiredHeight { get; private set; }
/// <summary>
/// Determines the text will be word wrapped. This is true, when the maximum size has been set.
/// </summary>
public bool WordWrap { get; private set; }
/// <summary>
/// Constructor.
/// </summary>
public AutoRichLabel()
{
InitializeComponent();
}
/// <summary>
/// Overrides the width and height with the measured width and height
/// </summary>
public void Fit()
{
base.Width = this.DesiredWidth;
base.Height = this.DesiredHeight;
}
/// <summary>
/// Will be called when the rich text content of the control changes.
/// </summary>
private void rtb_ContentsResized(object sender, ContentsResizedEventArgs e)
{
this.AutoSize = false; // Disable auto size, else it will break everything
this.WordWrap = this.MaximumSize.Width > 0; // Enable word wrap when the maximum width has been set.
this.DesiredWidth = this.rtb.WordWrap ? this.MaximumSize.Width : e.NewRectangle.Width; // Measure width.
this.DesiredHeight = this.MaximumSize.Height > 0 && this.MaximumSize.Height < e.NewRectangle.Height ? this.MaximumSize.Height : e.NewRectangle.Height; // Measure height.
this.Fit(); // Override width and height.
}
}
The syntax of the rich text format is quite simple:
Paragraph:
{\pard This is a paragraph!\par}
Bold / Italic / Underline text:
\b bold text\b0
\i italic text\i0
\ul underline text\ul0
Alternate color using color table:
{\colortbl ;\red0\green77\blue187;}
{\pard The word \cf1 fish\cf0 is blue.\par
But please note: Always wrap every text in a paragraph. Also, different tags can be stacked (i.e. \pard\b\i Bold and Italic\i0\b0\par) and the space character behind a tag is ignored. So if you need a space behind it, insert two spaces (i.e. \pard The word \bBOLD\0 is bold.\par). To escape \ or { or }, please use a leading \.
For more information there is a full specification of the rich text format online.
Using this quite simple syntax you can produce something like you can see in the first image. The rich text content that was attached to the RtfContent property of my AutoRichLabel in the first image was:
{\colortbl ;\red0\green77\blue187;}
{\pard\b BOLD\b0 \i ITALIC\i0 \ul UNDERLINE\ul0 \\\{\}\par}
{\pard\cf1\b BOLD\b0 \i ITALIC\i0 \ul UNDERLINE\ul0\cf0 \\\{\}\par}
If you want to enable word wrap, please set the maximum width to a desired size. However, this will fix the width to the maximum width, even when the text is shorter.
Have fun!
There is an excellent article from 2009 on Code Project named "A Professional HTML Renderer You Will Use" which implements something similar to what the original poster wants.
I use it successfully within several projects of us.
Very simple solution:
Add 2 labels on the form, LabelA and LabelB
Go to properties for LabelA and dock it to left.
Go to properties for LabelB and dock it to left as well.
Set Font to bold for LabelA .
Now the LabelB will shift depending on length of text of LabelA.
That's all.
I Would also be interested in finding out if it is possible.
When we couldn't find a solution we resorted to Component Ones 'SuperLabel' control which allows HTML markup in a label.
Realising this is an old question, my answer is more for those, like me, who still may be looking for such solutions and stumble upon this question.
Apart from what was already mentioned, DevExpress's LabelControl is a label that supports this behaviour - demo here. Alas, it is part of a paid library.
If you're looking for free solutions, I believe HTML Renderer is the next best thing.
A FlowLayoutPanel works well for your problem. If you add labels to the flow panel and format each label's font and margin properties, then you can have different font styles. Pretty quick and easy solution to get working.
Yeah.
You can implements, using HTML Render.
For you see, click on the link: https://htmlrenderer.codeplex.com/
I hope this is useful.

Resources