I have a very simple Storyboard with an Int32Animation that targets a custom DP on my class.
I have an OnChanged callback for my DP, that does a Console.WriteLine("Current Value: " + MyDP).
When I run the storyboard, I can see the Console output just fine, and when I pause the storyboard, the Console output stops, BUT, when I resume the storyboard, the DP is NOT the next value at it should be. It continues increasing even though the storyboard has stopped.
Has anyone had anything like this happen to them?
here is a code snippet of what Im doing
Int32Animation element = new Int32Animation();
element.From = 0;
element.To = targetTo;
element.Duration = dur;
Storyboard.SetTargetProperty(element, new PropertyPath(CurrentFrameProperty));
_filmstripStoryboard = new Storyboard {SpeedRatio = this.FrameRate};
_filmstripStoryboard.Children.Add(element);
public void Start()
{
_filmstripStoryboard.Begin(this, true);
}
public void Pause()
{
_filmstripStoryboard.Pause(this);
}
public void Unpause()
{
_filmstripStoryboard.Resume(this);
}
There's a thread in the MSDN forums where Microsoft confirms that this behavior (the current value continuing even when the storyboard is paused for a while) is a bug in the then-current (as of 2006) release of WPF. The forum thread includes a suggested workaround, specifically, save off the current position when you pause, and manually seek back to that same position when you resume.
The thread mentions that they were thinking about fixing the bug in a future version, but I do not know whether .NET 3.5 or 4.0 actually fixed this bug or not.
EDIT: It appears that the bug is fixed in .NET 4.0 -- I was able to pause and then resume an animation without it jumping forward across the intervening time. (I didn't test in 3.5.)
Now there is no bug in 4.0 . Below code works fine.
<Window
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
x:Class="WpfAnimation.Win1805787"
xmlns:Local="clr-namespace:WpfAnimation"
x:Name="MyWindow"
Title="Win1805787"
Width="640" Height="480">
<Grid x:Name="LayoutRoot">
<StackPanel HorizontalAlignment="Center" VerticalAlignment="Center">
<TextBox HorizontalAlignment="Right" VerticalAlignment="Top" Width="75" Text="{Binding CurrentFrame, ElementName=MyWindow}" Height="20" Margin="5,0,5,5"/>
<Button x:Name="Play1" Content="Play" HorizontalAlignment="Left" VerticalAlignment="Top" Width="75" Click="Play_Click" Margin="5,0,5,5"/>
<Button x:Name="Pause1" Content="Pause" HorizontalAlignment="Right" VerticalAlignment="Top" Width="75" Click="Pause_Click" Margin="5,0,5,5"/>
<Button x:Name="Resume1" Content="Resume" HorizontalAlignment="Right" VerticalAlignment="Top" Width="75" Click="Resume_Click" Margin="5,0,5,5"/>
</StackPanel>
</Grid>
</Window>
/////////////////////////
using System;
using System.Collections.Generic;
using System.Text;
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;
using System.Windows.Media.Animation;
namespace WpfAnimation
{
/// <summary>
/// Interaction logic for Win1805787.xaml
/// </summary>
public partial class Win1805787 : Window
{
public Win1805787()
{
this.InitializeComponent();
_createStoryBoard();
// Insert code required on object creation below this point.
}
public int CurrentFrame
{
get { return (int)GetValue(CurrentFrameProperty); }
set { SetValue(CurrentFrameProperty, value); }
}
// Using a DependencyProperty as the backing store for CurrentFrame. This enables animation, styling, binding, etc...
public static readonly DependencyProperty CurrentFrameProperty =
DependencyProperty.Register("CurrentFrame", typeof(int), typeof(Win1805787),
new PropertyMetadata(0, new PropertyChangedCallback(OnValueChanged)));
private static void OnValueChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
}
Storyboard _filmstripStoryboard;
void _createStoryBoard()
{
Int32Animation element = new Int32Animation();
element.From = 0;
element.To = 100;
element.Duration = Duration.Plus(new Duration(new TimeSpan(1000000000)));
Storyboard.SetTargetProperty(element, new PropertyPath(CurrentFrameProperty));
_filmstripStoryboard = new Storyboard { SpeedRatio = 0.5 };
_filmstripStoryboard.Children.Add(element);
}
private void Play_Click(object sender, System.Windows.RoutedEventArgs e)
{
_filmstripStoryboard.Begin(this, true);
}
private void Pause_Click(object sender, System.Windows.RoutedEventArgs e)
{
_filmstripStoryboard.Pause(this);
}
private void Resume_Click(object sender, System.Windows.RoutedEventArgs e)
{
_filmstripStoryboard.Resume(this);
}
}
}
Related
I'm exploring WPF and seeing if I can work my way to using a full MVVM approach. For now, I think I need to learn how to reference my own custom objects/commands that I've defined in a nearby namespace.
This is my folder structure:
This is my XAML
<Window x:Class="WpfApp1.MainWindow"
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:WpfApp1"
xmlns:commands="clr-namespace:WpfApp1.Commands"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<Window.CommandBindings>
<CommandBinding
Command="commands:WordSearchCommand"
CanExecute="CanExecuteChanged"/>
</Window.CommandBindings>
<Grid>
<StackPanel Orientation="Horizontal">
<Label Target="{Binding ElementName=wordSearchBox}">Word _Search:</Label>
<TextBox
Name="wordSearchBox"
Height="25"
Width="600"
VerticalAlignment="Top"
SpellCheck.IsEnabled="True"
Text="{Binding Path=SearchWord}">
</TextBox>
<Button Height="25" Width="100" VerticalAlignment="Top" Command="{Binding Path=WordSearchCommand}" CommandParameter="{Binding Path=SearchWord}">Search</Button>
</StackPanel>
</Grid>
</Window>
This is my MainWindow code-behind:
using AppLogicCommandsAndQueries;
using System.Windows;
using WpfApp1.ViewModels;
namespace WpfApp1
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
Bootstrapper.Bootstrap();
InitializeComponent();
DataContext = new WordSearchViewModel();
}
}
}
This is my WordSearchCommand definition:
using System;
using System.Windows;
using System.Windows.Input;
namespace WpfApp1.Commands
{
public class WordSearchCommand : ICommand
{
private string previousSearch;
public event EventHandler CanExecuteChanged = delegate (object s, EventArgs e)
{
MessageBox.Show("word search can execute changed");
};
public bool CanExecute(object parameter)
{
return previousSearch != (string)parameter;
}
public void Execute(object parameter)
{
// if online check online db else check offline db
MessageBox.Show("word search command");
previousSearch = (string)parameter;
}
}
}
I've tried rebuilding, switching CPU targets, switching to Release mode and back, etc. There's got to be a coding error, right?
This is the error displayed in my build output:
WpfApp1\MainWindow.xaml(14,134,14,151): error CS1061: 'MainWindow' does not contain a definition for 'CanExecuteChanged' and no accessible extension method 'CanExecuteChanged' accepting a first argument of type 'MainWindow' could be found (are you missing a using directive or an assembly reference?)
It seems like I don't understand C# events or WPF event routing very well. I was able to get the desired behavior by changing the CanExecuteChanged event handler on my WordSearchCommand.
Before I had:
public event EventHandler CanExecuteChanged = delegate (object s, EventArgs e)
{
MessageBox.Show("word search can execute changed");
};
Which would never execute (I never saw the message box). Also, the CanExecute method would only get called once.
Now, I have:
public event EventHandler CanExecuteChanged
{
add { CommandManager.RequerySuggested += value; }
remove { CommandManager.RequerySuggested -= value; }
}
and now my CanExecute method gets called a ton (a bunch at the beginning, and basically anytime I interact with the Window?).
I tried adding event accessors before but didn't realize I needed to remove the delegate signature for the accessor definitions to become syntactically valid. I definitely took inspiration from a RelayCommand definition as well as other posts on StackOverflow. I'm still not exactly sure what's going on here, but I've stumbled upon a solution that I can use for now.
Is there an API to lock the orientation of my WPF application to run only in the landscape mode.
i could find event that could fire on orientation change, but is there a way to lock it
Microsoft.Win32.SystemEvents.DisplaySettingsChanged
I dont think there is a way in WPF to lock it, you could provide a rotation layout transformation to the root container. It is not actually locking the orientation but rotating your application. Something like this,
<Grid.RenderTransform>
<RotateTransform Angle="-90"/>
</Grid.RenderTransform>
Read here,
Layout Transformation
For anyone who arrives at this question and is left needing more detail, here is a fully detailed answer...
It appears that as tablet rotation support was done at the individual vendor level, there isn’t much support for it in Windows 7 / 8 other than detecting that there was a change in rotation. The screen can still be pseudo-locked into a landscape format with a bit of XAML sleight of hand. Here’s some sample code that I knocked up last night and tested on my tablet PC…
MainWindow.xaml
<Window x:Class="LockRotationWpf.MainWindow"
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:LockRotationWpf"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<Grid HorizontalAlignment="Center"
VerticalAlignment="Center"
RenderTransformOrigin="0.5,0.5" >
<Grid.RenderTransform >
<RotateTransform Angle="{Binding Angle}" />
</Grid.RenderTransform>
<StackPanel>
<TextBlock FontSize="100" Text="Landscape"/>
<TextBlock Text="{Binding Angle}" TextAlignment="Center"/>
</StackPanel>
</Grid>
</Window>
MainWindow.xaml.cs
using System.Windows;
using LockRotationWpf.ViewModels;
namespace LockRotationWpf
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
var vm = new MainWindowViewModel();
DataContext = vm;
}
}
}
ViewModels\MainWindowViewModel.cs
using System;
using System.ComponentModel;
using System.Runtime.CompilerServices;
using LockRotationWpf.Annotations;
namespace LockRotationWpf.ViewModels
{
// Omit the INotifyPropertyChanged implementation if already implemented on a base ViewModel class.
public class MainWindowViewModel : INotifyPropertyChanged
{
public double _angle;
public MainWindowViewModel()
{
Microsoft.Win32.SystemEvents.DisplaySettingsChanged += DisplaySettingsChanged;
}
private void DisplaySettingsChanged(object sender, EventArgs e)
{
Angle = System.Windows.SystemParameters.PrimaryScreenHeight > System.Windows.SystemParameters.PrimaryScreenWidth ? -90 : 0;
}
public double Angle
{
get { return _angle; }
set
{
_angle = value;
OnPropertyChanged();
}
}
public event PropertyChangedEventHandler PropertyChanged;
[NotifyPropertyChangedInvocator]
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
}
Here are a couple of screen-shots that show the change in orientation. If you’re running an App full screen you won’t get the window chrome and you’d be unaware that the screen had actually rotated. Any on-screen keyboard which opens in response to a text input will of course still pop-up in the portrait position, but the fact that the content is always landscape should give the operator enough of a hint to turn the tablet around for input.
I hope that helps anyone who lands here.
This worked for me
[System.Runtime.InteropServices.DllImport("user32", EntryPoint = "SetDisplayAutoRotationPreferences")]
private static extern UInt32 SetDisplayAutoRotationPreferences8(UInt32 orientation);
[System.Runtime.InteropServices.DllImport("kernel", EntryPoint = "SetDisplayAutoRotationPreferences")]
private static extern UInt32 SetDisplayAutoRotationPreferences7(UInt32 orientation);
public MyWindow()
{
InitializeComponent();
}
And on load of my main window :
SetDisplayAutoRotationPreferences8(2);//Lanscape
At some point in my Silverlight application I need to perform a heavy operation which freezes the UI thread for about 4 seconds. Before actually performing the operation I am trying to display a simple text indicator via a TextBlock control.
StatusTextBlock.Text = "Performing Some Operation...";
System.Threading.Thread.Sleep(4000); // Just as an example
The problem is that the UI thread freezes before the text of the TextBlock control gets updated. How can I get the notification text shown before the operation begins?
Also, taking the heavy operation to a background thread is not an option for me, as it deals with UI objects (it switches the visual root of the application) and should be executed on the UI thread.
My suggestion is to take it off UI thread and use background thread...
StatusTextBox.Text = "Before Sleep";
BackgroundWorker bw = new BackgroundWorker();
bw.DoWork += new DoWorkEventHandler(bw_DoWork);
bw.RunWorkerCompleted += new RunWorkerCompletedEventHandler(bw_RunWorkerCompleted);
bw.RunWorkerAsync();
void bw_DoWork(object sender, DoWorkEventArgs e)
{
System.Threading.Thread.Sleep(8000);}
void bw_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
StatusTextBox.Text = "after Sleep";
}
I found a solution with the help of Jeff Prosise's blog post: http://www.wintellect.com/cs/blogs/jprosise/archive/2008/10/25/cool-silverlight-trick-5.aspx
The idea is to delay the call performing a long running task till a Silverlight UI rendering event fires. For this I used the CompositionTarget.Rendering event. I subscribed to it in the constructor of the user control:
CompositionTarget.Rendering += this.CompositionTargetRendering;
After I update the text of a TextBlock control I set a private flag, which indicates that some processing should be made in the event handler:
StatusTextBlock.Text = "Performing Some Operation...";
this.processRenderingEvent = true;
And here is the code of the handler:
private void CompositionTargetRendering(Object sender, EventArgs e)
{
if (this.processRenderingEvent)
{
if (++this.renderingEventCounter == 2)
{
System.Threading.Thread.Sleep(4000); // Example of long running task
this.processRenderingEvent = false;
}
}
}
An important point to mention here is that I use a private integer field renderingEventCounter to begin the long running task not the first time the event fires, but the second. The reason for this is that the CompositionTarget.Rendering event is fired just before the Silverlight UI rendering engine draws a new frame on the application's display surface, which means that at the first time the event fires the text of the TextBlock control is not yet updated. But it will be updated the second time.
I think you should implement the BackgroundWorker thread is tsiom's answer, but use the Dispatcher.BeginInvoke to operate on the UI objects, here is a MSDN article on how to use the method: http://msdn.microsoft.com/en-us/library/cc190824%28v=vs.95%29.aspx
Also, see another StackOverflow question for a more comprehensive scenario using the Dispatcher: Understanding the Silverlight Dispatcher
I just ran into this situation myself. The problem (I think) is that before the text gets updated you have already begun the intensive operation, so you have to wait.
What you can do is to attach a listened to some method on the textbox that only gets called once the text is updated (textChanged perhaps?) and THEN call your intensive operation.
This seems hackish to me though...
This is ugly but it works. By delaying the initiliazation of the long running operation using a DispatcherTimer we can allow the UI to be updated before the operation is started.
XAML:
<UserControl x:Class="SilverlightApplication13.MainPage"
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"
d:DesignHeight="300"
d:DesignWidth="400">
<Grid x:Name="LayoutRoot"
Background="White">
<StackPanel>
<Border x:Name="Brd01"
Visibility="Collapsed"
Background="Red">
<TextBlock VerticalAlignment="Center"
Margin="30">Sleeping for 4 seconds...</TextBlock>
</Border>
<Border x:Name="Brd02"
Visibility="Collapsed"
Background="Lime">
<TextBlock VerticalAlignment="Center"
Margin="30">Done!</TextBlock>
</Border>
<Button Content="Start Operation"
Click="Button_Click_1"></Button>
</StackPanel>
</Grid>
</UserControl>
Code-behind:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Threading;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;
using System.Windows.Threading;
namespace SilverlightApplication13
{
public partial class MainPage : UserControl
{
public MainPage()
{
InitializeComponent();
}
private void Button_Click_1(object sender, RoutedEventArgs e)
{
//Show the "working..." message
Brd01.Visibility = System.Windows.Visibility.Visible;
//Initialize a timer with a delay of 0.1 seconds
var timer = new DispatcherTimer();
timer.Interval = TimeSpan.FromMilliseconds(100);
timer.Tick += Timer_Tick;
timer.Start();
}
private void Timer_Tick(object sender, EventArgs e)
{
//Start the long running operation
Thread.Sleep(4000);
Brd01.Visibility = System.Windows.Visibility.Collapsed;
Brd02.Visibility = System.Windows.Visibility.Visible;
//Kill the timer so it will only run once.
(sender as DispatcherTimer).Stop();
(sender as DispatcherTimer).Tick -= Timer_Tick;
}
}
}
How to kill one button's event when a new button is clicked.
I have one event (Button G) running.(has a while loop waiting for some input).
I have a another button for quit operation.
Now. I cannot click any other button when button G's event is running.
How can I solve that?
Thanks
Hi, #Grokodile thank you for your code. So I commented your code here, Should I put my job logic code where I commented below? Thans
using System;
using System.ComponentModel;
using System.Threading;
using System.Windows;
using System.Windows.Threading;
namespace WpfApplication1
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
private BackgroundWorker _worker;
public MainWindow()
{
InitializeComponent();
}
private void RunButtonClickHandler(object sender, RoutedEventArgs e)
{
_worker = new BackgroundWorker {WorkerSupportsCancellation = true};
_worker.DoWork += BackgroundWorkerTask;
_worker.RunWorkerAsync();
//I should Put my job logic here, right?
}
private void StopButtonClickHandler(object sender, RoutedEventArgs e)
{
if (_worker != null && _worker.IsBusy) _worker.CancelAsync();
//I should Put my job logic here, right?
}
private void BackgroundWorkerTask(object sender, DoWorkEventArgs e)
{
// this runs on the BackgroundWorker thread.
while (_worker.CancellationPending == false)
{
Thread.Sleep(500);
// You have to use the Dispatcher to transfer the effects of
// work done in the worker thread back onto the UI thread.
Dispatcher.BeginInvoke(new Action(UpdateTime), DispatcherPriority.Normal, null);
}
}
private void UpdateTime()
{
// Dispatcher runs this on the UI thread.
timeTextBlock.Text = DateTime.Now.ToString();
}
}
}
Futher to what H.B. and dowhilefor have said, here is a sample that shows starting a task on a background thread using BackgroundWorker with one Button and ending it with another Button, note the use of Dispatcher.BeginInvoke:
XAML
<Window x:Class="WpfApplication1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow"
Height="80"
Width="640"
FontSize="16">
<DockPanel VerticalAlignment="Center">
<Button Margin="10,0"
x:Name="runButton"
DockPanel.Dock="Left"
Click="RunButtonClickHandler">Run</Button>
<Button Margin="10,0"
x:Name="stopButton"
DockPanel.Dock="Left"
Click="StopButtonClickHandler">Stop</Button>
<TextBlock Margin="10,0">The Time Is Now:</TextBlock>
<TextBlock x:Name="timeTextBlock"
Margin="10,0" />
</DockPanel>
</Window>
Code Behind
using System;
using System.ComponentModel;
using System.Threading;
using System.Windows;
using System.Windows.Threading;
namespace WpfApplication1
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
private BackgroundWorker _worker;
public MainWindow()
{
InitializeComponent();
}
private void RunButtonClickHandler(object sender, RoutedEventArgs e)
{
_worker = new BackgroundWorker {WorkerSupportsCancellation = true};
_worker.DoWork += BackgroundWorkerTask;
_worker.RunWorkerAsync();
}
private void StopButtonClickHandler(object sender, RoutedEventArgs e)
{
if (_worker != null && _worker.IsBusy) _worker.CancelAsync();
}
private void BackgroundWorkerTask(object sender, DoWorkEventArgs e)
{
// this runs on the BackgroundWorker thread.
while (_worker.CancellationPending == false)
{
Thread.Sleep(500);
// You have to use the Dispatcher to transfer the effects of
// work done in the worker thread back onto the UI thread.
Dispatcher.BeginInvoke(new Action(UpdateTime), DispatcherPriority.Normal, null);
}
}
private void UpdateTime()
{
// Dispatcher runs this on the UI thread.
timeTextBlock.Text = DateTime.Now.ToString();
}
}
}
EDIT - A Little More Explanation
RunButtonClickHandler
Creates and initializes a BackgroundWorker so that it supports cancellation.
Attaches a DoWorkEventHandler to the DoWork event, i.e. BackgroundWorkerTask.
Starts excecution of the background operation with the call to RunWorkerAsync, i.e. creates a new thread (actually it uses a thread from the thread pool) and runs the code in BackgroundWorkerTask on that thread.
BackgroundWorkerTask
If you want to do work that would otherwise cause the UI to freeze when running on the main UI thread (e.g. search for undiscovered prime numbers) then you do it here in the background thread.
UpdateTime
All WPF Controls inherit from DispatcherObject and are associated with a Dispatcher which manages the execution of work done in the UI's single thread. If you need to do work such as setting the text of a TextBlock, you can't do it from the background thread, trying to do so will cause an exception to be thrown. UpdateTime is queued back onto the UI thread by the Dispatcher when Dispatcher.BeginInvoke is called from BackgroundWorkerTask. This way you can get the results of work done in the background before cancelling the background threads execution.
StopButtonClickHandler
Changes _worker.CancellationPending to true with the call to CancelAsync causing the while loop to exit and thus for execution to leave the BackgroundWorkerTask event handler.
In short, you can do work in two places, either in BackgroundWorkerTask or in UpdateTime, but you can only carry out changes to UI elements from UpdateTime.
Don't do spin-waiting operations on the UI-thread, use a new Thread or a Timer for example.
You can't. The UI is running in a single thread, usually called the main or ui thread. With your while loop you are blocking the whole ui thread, thus you can't receive any further input.
I suggest you check out BackgroundWorker class and maybe check some more MSDN articles about how the threading and background tasks should be designed to work properly in an ui enviroment.
I need to set the font family for the next text to be written in a RichTextBox.
I tried setting that with...
<RichTextBox x:Name="RichTextEditor" MaxWidth="1000" SpellCheck.IsEnabled="True"
FontFamily="{Binding ElementName=TextFontComboBox, Path=SelectedItem}"
FontSize="{Binding ElementName=TextSizeComboBox, Path=SelectedValue}"
Width="Auto" Height="Auto" HorizontalScrollBarVisibility="Auto"
VerticalScrollBarVisibility="Auto" />
...but it changed the whole text. I suppose that with the Selection property I can restrict the change to be applied just to the selected area. But how for the next -not yet typed- text?
In order to set the FontFamily based on the cursor position you need to define a custom control with a dependency property that helps insert a new Run section by overriding the OnTextInput method.
I included most of the code, you'll need to modify the namespaces to fit your development environment.
The code uses a ViewModel to manage the available fonts and manage if the font changed.
This code is only a prototype and does not deal with focusing issues between the two controls.
To use this code:
1- Type some text in the RichTectBox.
2- Change the font in the ComboBox.
3- Tab back to the RichTextBox.
4- Type some more text.
Here is the custom RichTextBox control:
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
namespace RichTextboxFont.Views
{
public class RichTextBoxCustom : RichTextBox
{
public static readonly DependencyProperty CurrentFontFamilyProperty =
DependencyProperty.Register("CurrentFontFamily",
typeof(FontFamily), typeof
(RichTextBoxCustom),
new FrameworkPropertyMetadata(new FontFamily("Tahoma"),
FrameworkPropertyMetadataOptions.BindsTwoWayByDefault,
new PropertyChangedCallback(OnCurrentFontChanged)));
public FontFamily CurrentFontFamily
{
get
{
return (FontFamily)GetValue(CurrentFontFamilyProperty);
}
set
{
SetValue(CurrentFontFamilyProperty, value);
}
}
private static void OnCurrentFontChanged(DependencyObject o, DependencyPropertyChangedEventArgs e)
{}
protected override void OnTextInput(TextCompositionEventArgs e)
{
ViewModels.MainViewModel mwvm = this.DataContext as ViewModels.MainViewModel;
if ((mwvm != null) && (mwvm.FontChanged))
{
TextPointer textPointer = this.CaretPosition.GetInsertionPosition(LogicalDirection.Forward);
Run run = new Run(e.Text, textPointer);
run.FontFamily = this.CurrentFontFamily;
this.CaretPosition = run.ElementEnd;
mwvm.FontChanged = false;
}
else
{
base.OnTextInput(e);
}
}
}
}
Here is the XAML:
<Window x:Class="RichTextboxFont.Views.MainView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:RichTextboxFont.Views"
xmlns:ViewModels="clr-namespace:RichTextboxFont.ViewModels"
Title="Main Window"
Height="400" Width="800">
<DockPanel>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition/>
</Grid.RowDefinitions>
<ComboBox ItemsSource="{Binding Path=Fonts}"
SelectedItem="{Binding Path=SelectedFont, Mode=TwoWay}"/>
<local:RichTextBoxCustom Grid.Row="1"
CurrentFontFamily="{Binding Path=SelectedFont, Mode=TwoWay}"
FontSize="30"/>
</Grid>
</DockPanel>
</Window>
Here is the ViewModel:
If you do not use view models, let me know and I'll add the base class code too; otherwise, google/stackoverflow can help you too.
using System.Collections.ObjectModel;
using System.Windows.Media;
namespace RichTextboxFont.ViewModels
{
public class MainViewModel : ViewModelBase
{
#region Constructor
public MainViewModel()
{
FontFamily f1 = new FontFamily("Georgia");
_fonts.Add(f1);
FontFamily f2 = new FontFamily("Tahoma");
_fonts.Add(f2);
}
private ObservableCollection<FontFamily> _fonts = new ObservableCollection<FontFamily>();
public ObservableCollection<FontFamily> Fonts
{
get
{
return _fonts;
}
set
{
_fonts = value;
OnPropertyChanged("Fonts");
}
}
private FontFamily _selectedFont = new FontFamily("Tahoma");
public FontFamily SelectedFont
{
get
{
return _selectedFont;
}
set
{
_selectedFont = value;
FontChanged = true;
OnPropertyChanged("SelectedFont");
}
}
private bool _fontChanged = false;
public bool FontChanged
{
get
{
return _fontChanged;
}
set
{
_fontChanged = value;
OnPropertyChanged("FontChanged");
}
}
#endregion
}
}
Here is the Window code-behind where I initialise the ViewModel:
using System.Windows;
namespace RichTextboxFont.Views
{
public partial class MainView : Window
{
public MainView()
{
InitializeComponent();
this.DataContext = new ViewModels.MainViewModel();
}
}
}
There's a much easier way to do this: Implement a toolbar for your RichTextBox.
Unlike WinForms, the RichTextBox in WPF doesn't come with a toolbar by default, but it's really easy to create one yourself. The RichTextBox automatically handles many EditingCommands, so it's just a matter of creating a toolbar and some buttons. Microsoft has provided sample code for this at the bottom of the RichTextBox Overview on MSDN.
Unfortunately, those editing commands don't include setting the FontFace property of the selection, though you can create a ComboBox on the toolbar that can trigger the change with an event handler in the codebehind file.
That's the approach taken in this CodePlex article by Gregor Pross: WPF RichTextEditor
The project is commented in German, but the source itself is very clearly written. The codebehind used for his font selector ComboBox looks like this:
private void Fonttype_DropDownClosed(object sender, EventArgs e)
{
string fontName = (string)Fonttype.SelectedItem;
if (fontName != null)
{
RichTextControl.Selection.ApplyPropertyValue(System.Windows.Controls.RichTextBox.FontFamilyProperty, fontName);
RichTextControl.Focus();
}
}
The main reason that people struggle with the FontFace selection is that after the font selection has been made, you must return focus to the RichTextBox. If the user must manually press tab or click into the RichTextBox, a new text selection gets created and you lose the formatting options you've chosen.
One of the answers to this StackOverflow question discusses that problem.
WPF Richtextbox FontFace/FontSize
This isn't exactly a trivial answer.
To do inline text formatting in a Rich TextBox like you want you will have to modify the Document property of the RichTextBox. Very simply, something like this will work
<RichTextBox >
<RichTextBox.Document>
<FlowDocument>
<Paragraph>
<Run>Something</Run>
<Run FontWeight="Bold">Something Else</Run>
</Paragraph>
</FlowDocument>
</RichTextBox.Document>
</RichTextBox>
I think you could create a custom Control that creates a new block element and sets the font properties you need based on the user input.
For example, If the user types something then presses bold. You would want to wrap the previous text in a run and create a new run element setting the FontWeight to bold then the subsequent text will be wrapped in the bolded run.
Again, not a trivial solution but I can't think of any other way to accomplish what you are after.