This is the kind of thing that WPF is so great for...and is a complete nightmare to do in WinForms. You can download the source using the link at the bottom of the post.
Click here for the full screen shot.
Download source code
public class Window1AutomationPeer : WindowAutomationPeer
{
public Window1AutomationPeer(Window1 owner)
: base(owner)
{
}
protected override string GetItemStatusCore()
{
// use XmlSerializer to serialize custom data
XmlSerializer serializer = new XmlSerializer(typeof(CustomData));
CustomData data = new CustomData(...);
StringWriter stringWriter = new StringWriter(CultureInfo.InvariantCulture);
serializer.Serialize(stringWriter, data);
return stringWriter.ToString();
}
}
The example contains a working example complete with app, automation-client and XML serialization.
public Window1()
{
PresentationTraceSources.Refresh();
PresentationTraceSources.MarkupSource.Switch.Level = SourceLevels.All;
PresentationTraceSources.MarkupSource.Listeners.Add(new DefaultTraceListener());
...
}
<Window.Resources>
<my:DataResource x:Key="cp" BindingTarget="{Binding MyCommandParameter}"/>
</Window.Resources>
<TextBox>
<TextBox.InputBindings>
<MouseBinding
Command="ApplicationCommands.Open"
MouseAction="LeftDoubleClick">
<MouseBinding.CommandParameter>
<my:DataResourceBinding DataResource="{StaticResource cp}"/>
</MouseBinding.CommandParameter>
</MouseBinding>
</TextBox.InputBindings>
</TextBox>
public class IntValidationRule : ValidationRule
{
public int Max { get; set; }
public int Min { get; set; }
public override ValidationResult Validate(object value, CultureInfo cultureInfo)
{
try
{
int i = Convert.ToInt32(value);
return (i < Min || i > Max) ?
new ValidationResult(false, "int out of range") :
new ValidationResult(true, null);
}
catch (FormatException fe)
{
return new ValidationResult(false, fe.Message);
}
}
}
...the DataResources and the associated bindings should then simply be set up like this:
<Window.Resources>
<my:DataResource x:Key="max" BindingTarget="{Binding Max}"/>
<my:DataResource x:Key="min" BindingTarget="{Binding Min}"/>
</Window.Resources>
<TextBox>
<TextBox.Text>
<Binding UpdateSourceTrigger="PropertyChanged" Path="Value">
<Binding.ValidationRules>
<my:IntValidationRule
ValidatesOnTargetUpdated="True"
Min="{my:DataResourceBinding DataResource={StaticResource min}}"
Max="{my:DataResourceBinding DataResource={StaticResource max}}">
</my:IntValidationRule>
</Binding.ValidationRules>
</Binding>
</TextBox.Text>
</TextBox>
private void Window1_Loaded(object sender, RoutedEventArgs e)
{
// do this to get the HWND and set up the window hook
WindowInteropHelper helper = new WindowInteropHelper(this);
HwndSource.FromHwnd(helper.Handle).AddHook(HwndSourceHookHandler);
}
private IntPtr HwndSourceHookHandler(IntPtr hwnd, int msg, IntPtr wParam,
IntPtr lParam, ref bool handled)
{
// this is the window hook handler
}
Download sample app with source code
<Trigger my:TriggerTracing.TriggerName="BoldWhenMouseIsOver"
my:TriggerTracing.TraceEnabled="True"
Property="IsMouseOver"
Value="True">
<Setter Property="FontWeight" Value="Bold"/>
</Trigger>
private IEnumerable<TextElement> GetRunsAndParagraphs(FlowDocument doc)
{
// use the GetNextContextPosition method to iterate through the
// FlowDocument
for (TextPointer position = doc.ContentStart;
position != null && position.CompareTo(doc.ContentEnd) <= 0;
position = position.GetNextContextPosition(LogicalDirection.Forward))
{
if (position.GetPointerContext(LogicalDirection.Forward) ==
TextPointerContext.ElementEnd)
{
// return solely the Runs and Paragraphs. all other elements are
// ignored since they aren't supported by FormattedText.
Run run = position.Parent as Run;
if (run != null)
{
yield return run;
}
else
{
Paragraph para = position.Parent as Paragraph;
if (para != null)
{
yield return para;
}
}
}
}
}
The rest of process is fairly straightforward. Use the GetRunsAndParagraphs method and build up the formatting on a FormattedText object:
public FormattedText GetFormattedText(FlowDocument doc)
{
if (doc == null)
{
throw new ArgumentNullException("doc");
}
FormattedText output = new FormattedText(
GetText(doc),
CultureInfo.CurrentCulture,
doc.FlowDirection,
new Typeface(doc.FontFamily, doc.FontStyle, doc.FontWeight, doc.FontStretch),
doc.FontSize,
doc.Foreground);
int offset = 0;
foreach (TextElement el in GetRunsAndParagraphs(doc))
{
Run run = el as Run;
if (run != null)
{
int count = run.Text.Length;
output.SetFontFamily(run.FontFamily, offset, count);
output.SetFontStyle(run.FontStyle, offset, count);
output.SetFontWeight(run.FontWeight, offset, count);
output.SetFontSize(run.FontSize, offset, count);
output.SetForegroundBrush(run.Foreground, offset, count);
output.SetFontStretch(run.FontStretch, offset, count);
output.SetTextDecorations(run.TextDecorations, offset, count);
offset += count;
}
else
{
offset += Environment.NewLine.Length;
}
}
return output;
}
private string GetText(FlowDocument doc)
{
StringBuilder sb = new StringBuilder();
foreach (TextElement el in GetRunsAndParagraphs(doc))
{
Run run = el as Run;
sb.Append(run == null ? Environment.NewLine : run.Text);
}
return sb.ToString();
}
You can download the source code and demo here.
<Window.Resources>
<ContextMenu x:Shared="False" x:Key="menu">
<ContextMenu.Items>
<MenuItem Header="A Menu Item"/>
</ContextMenu.Items>
</ContextMenu>
</Window.Resources>
<StackPanel>
<Button Content="Button1" ContextMenu="{StaticResource menu}" Click="Btn_Click"/>
<Button Content="Button2" ContextMenu="{StaticResource menu}" Click="Btn_Click"/>
</StackPanel>
private void Btn_Click(object sender, RoutedEventArgs e)
{
Button b = (Button)sender;
MenuItem item = (MenuItem)b.ContextMenu.Items[0];
item.IsChecked = !item.IsChecked;
}
Visit the MSDN page for more info about this attribute.
DataObject.AddPastingHandler(dependencyObject, handler); DataObject.RemovePastingHandler(dependencyObject, handler); DataObject.AddCopyingHandler(dependencyObject, handler); DataObject.RemovePastingHandler(dependencyObject, handler); DataObject.AddSettingDataHandler(dependencyObject, handler); DataObject.RemoveSettingDataHandler(dependencyObject, handler);Very useful!!
<Window x:Class="WpfLibrary.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:WpfLibrary">
<Window.Resources>
<SolidColorBrush Color="AliceBlue"/>
</Window.Resources>
<TextBox/>
</Window>
The URLs have been setup to represent multiple CLR namespaces in the AssemblyInfo of the assembly in which they are defined. Notice that both the TextBox and SolidColorBrush classes are referenced using the default XML namespace even though the classes are actually defined in different CLR namespaces.
[assembly:XmlnsDefinition("http://myorganization.com", "WpfLibrary")]
[assembly:XmlnsDefinition("http://myorganization.com", "WpfLibrary.Pens")]
[assembly:XmlnsDefinition("http://myorganization.com", "WpfLibrary.Brushes")]
Consumers of your assembly can then use the XML namespace to reference all of the associated CLR namespaces in one swoop:
<Window x:Class="WpfApplication1.Window1" xmlns:myorg="http://myorganization.com" </Window>
<CheckBox>
<TextBlock TextWrapping="Wrap"
Text="_This is a long piece of text attached to a checkbox."/>
</CheckBox>
There is a problem regarding accelerator keys here though. Look at the output from the last example:
<CheckBox>
<AccessText TextWrapping="Wrap"
Text="_This is a long piece of text attached to a checkbox."/>
</CheckBox>
This will ensure that the underscore is correctly displayed.