So I'm trying to use David Veeneman's Bindable WPF RichTextBox here in my .net 4.5 project. After adding the control and the ValueConverter in my code I noticed only the the public object Convert() will be triggered but the public object ConvertBack() not.
After reading the comments to this project I changed following parts of the control source code.
private static void OnDocumentChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var thisControl = (EcoRichTextBox)d;
if (thisControl.m_InternalUpdatePending > 0)
{
thisControl.m_InternalUpdatePending--;
return;
}
// Changed:
try
{
thisControl.TextBox.Document = (e.NewValue == null) ? new FlowDocument() : (FlowDocument)e.NewValue;
}
catch { }
thisControl.m_TextHasChanged = false;
}
And this Event Handler:
private void OnTextChanged(object sender, TextChangedEventArgs e)
{
// Set the TextChanged flag
m_TextHasChanged = true;
// Changed:
Document = TextBox.Document;
}
Now the the both method of the ValueConverter worked fine but events like private void OnNormalTextClick(object sender, RoutedEventArgs e) causes a FatalExecutionEngineError on Runtime.
So i wonder if there are major changes form WPF 3.5 to 4.5?
Or anybody have an idea to work around this?
Update
Binding in XAML
<uc:FsRichTextBox Margin="5"
Document="{Binding Path=Ereignis.Bericht,
Converter={StaticResource flowDocumentConverter},
UpdateSourceTrigger=PropertyChanged, NotifyOnSourceUpdated=True}" />
I ran the demo you linked here in VS2015 with target framework 4.0 and 4.5. It will not update when I take out the two way data binding.
Add to your RTB. Two way data binding and a name:
Mode=TwoWay
x:Name="EditBox"
I think rather than managing the text change yourself here, remove this:
// Changed:
Document = TextBox.Document;
Use an event handler to update the data.
Then in your event handler that is managing your updates (I am assuming a button click? And allow this to manage the update.
this.EditBox.UpdateDocumentBindings();
The x:name attribute is valuable.
This is all found in the source code.
If you can be more clear about how your project is arranged I can provide more detail. But for starters, I would do this. Stick more closely to the provided example.
Related
I'm using the classic WinForms version of the DevExpress XtraEditors. The WPF version makes it easy to get the editor's old value in the EditValueChanged event, but I don't see how to get the old value in the WinForms counterpart EditValueChanged event. If it can be obtained from within that event, how to do it?
https://documentation.devexpress.com/#windowsforms/DevExpressXtraEditorsRepositoryRepositoryItem_EditValueChangedtopic
RepositoryItemGridLookUpEdit class is not an editor itself. This class is only holding properties for in-place editors. So, to get editor's old value you must get the editor itself (from sender object) and use its BaseEdit.OldEditValue property.
Here is example:
private void repositoryItemGridLookUpEdit1_EditValueChanged(object sender, EventArgs e)
{
var baseEdit = (BaseEdit)sender;
if (baseEdit.OldEditValue.ToString() == "Some value")
{
//...
}
}
Consider the following code:
<Window xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<StackPanel>
<Slider ValueChanged="slider_ValueChanged/>
<TextBox x:Name="counter"/>
</StackPanel>
</Window>
and
namespace Project1
{
public partial class Window1 : Window
{
public MainWindow() { InitializeComponent(); }
void slider_ValueChanged(object sender,
RoutedPropertyChangedEventArgs<double> e)
{
counter.Text = e.NewValue.ToString();
}
}
}
Slider will raise its ValueChanged event during initialization while counter is still null.
This is an example of a larger problem that I've been running into using WPF, that UI events can fire at any time, and that there is no single place where I can put my initialization code so that it's guaranteed to run after all the pointers owned by the WPF system have been initialized but before any UI events have fired.
What is the most elegant way to deal with this? The fact that this specific example should use data binding is beside the point.
There are many ways to deal with this, depending on your situation
First off, you could simply recognize the fact that the object might not be initialized and check for that before processing. For example,
if (counter.Text != null)
counter.Text = e.NewValue.ToString();
Second, you could attach your events in the Loaded event of the object so they don't fire until after the object has been initialized.
void Counter_Loaded(object sender, EventArgs e)
{
slider.ValueChanged += Slider_ValueChanged;
}
void Counter_Unloaded(object sender, EventArgs e)
{
slider.ValueChanged -= Slider_ValueChanged;
}
And last of all, you can use WPF's Dispatcher to run events on the UI thread at a different DispatcherPriority. The default is Normal, which runs after Loaded, Render, and DataBind operations
Dispatcher.BeginInvoke(DispatcherPriority.DataBind,
new Action(delegate() { counter.Text = e.NewValue.ToString(); }));
The true answer to this question is to use the MVVM pattern where window code behind files contain little to no initialization code.
In this pattern, the UI is connected to the rest of the code with data binding only. You write special view-model classes that implement INotifyPropertyChanged and take your business logic and expose it as a series of properties that UI binds to.
Naturally, you fully control how your view-models initialize.
I'm using Microsoft's ReportViewer control in my WPF application. Since this is a WinForms component, I use the WindowsFormHost control.
I try to follow the MVVM pattern as supported by the WPF Application Framework, so I implemented a ReportViewModel which contains (amongst others) the current report name and the dataset (both can be selected by 'regular' WPF controls, that part works fine).
I'd like to be as "WPF-ish" as possible, so how would I properly set up the binding to the ReportViewer component (which is inside the WindowsFormHost control)? I need to set the ReportViewer.LocalReport.ReportEmbeddedResource property and have a call to ReportViewer.LocalReport.DataSources.Add (and possibly Clear) whenever the view models report name or dataset change. What's the proper way to do that?
Is there any chance to use one of the regular WPF binding mechanisms for that? If yes, how? If no, how would I set up the binding? (its my first 'real' WPF project, so don't be shy to post trivial solutions :) ...)
Thanks!
So far I've come up with the following solution (purly code-behind):
private void MyDataContextChanged(object sender, DependencyPropertyChangedEventArgs e) {
if (e.OldValue is ReportViewModel) {
var viewModel = e.OldValue as ReportViewModel;
viewModel.PropertyChanged -= ViewModelPropertyChanged;
}
if (DataContext is ReportViewModel) {
var viewModel = DataContext as ReportViewModel;
viewModel.PropertyChanged += ViewModelPropertyChanged;
SetReportData();
}
}
void ViewModelPropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e) {
if (e.PropertyName == "ReportName" || e.PropertyName == "ReportData")
SetReportData();
}
private void SetReportData() {
var viewModel = DataContext as ReportViewModel;
if (viewModel != null) {
var reportView = reportHost.Child as ReportViewer;
reportView.LocalReport.ReportEmbeddedResource = viewModel.ReportName;
reportView.LocalReport.DataSources.Clear();
reportView.LocalReport.DataSources.Add(new ReportDataSource("DataSet1", viewModel.ReportData as DataTable));
reportView.RefreshReport();
}
}
I'm still curious if there are any better solutions (I'm sure there are ...).
I'm trying to create a generalized event for my Close buttons, where they have to close the window but before that set focus to the owner window. I don't want to have an event for every file for that, because that'd be pretty unpractical since I have 30+ windows in my application. (So if I wanted to change that behavior, i'd have to change on 30 files everytime)
I'm not sure if that's the correct approach, but I tried making a MarkUp Extension which returns a delegate(object sender, RoutedEventArgs e) Here is the code:
delegate void RoutedDelegate(object sender, RoutedEventArgs e);
[MarkupExtensionReturnType(typeof(RoutedEvent))]
public class CloseWindowExtension : MarkupExtension
{
Window win = null;
public Window Win
{
get { return this.win; }
set { this.win = value; }
}
public CloseWindowExtension(Window win)
: base()
{
this.win = win;
}
public override object ProvideValue(IServiceProvider serviceProvider)
{
if (win == null)
{
throw new InvalidOperationException("The window must be specified!");
}
return new RoutedDelegate(delegate(object sender, RoutedEventArgs e)
{
Extensions.FocusClose(win);
});
}
}
The FocusClose method gets a window, closes it, but sets focus to its owner before. But I can't make it work. When i set my button in the xaml,
Button Click="{e:CloseWindow {Binding win}}"
(win is my Window name), I get the error message:
Click="{e:CloseWindow {Binding win}}" is not valid. '{e:CloseWindow {Binding win}}' is not a valid event handler method name. Only instance methods on the generated or code-behind class are valid. Line 28 Position 17.
Am I doing something wrong? Is this the best approach or do I have another options?
Thanks in advance!
Clark
You can't use a markup extension to set an event handler. Instead, you can use an attached behavior, which allows you to bind a command to an event.
See this article by Marlon Grech for details
.NET 4.5+ supports markup extensions for events, so you can implement what you wanted now :)
What would be the best way to save the window position and size in a WPF app?
Currently, I'm saving the window size and position of a WPF App. Here are the events I handle:
SourceInitialized : The saved info is loaded on to the window
WindowClosing : The current info is saved to the backing store
(I copied this from an example).
The problem is, when the window is minimized and restored, the settings from the last WindowClosing is retrieved.
Now, the StateChanged event fire AFTER the window has minimized, so it does not seem to be what i need.
Thanks
Do yourself and your users a favor and use the LocationChanged event and the SizeChanged event to save the settings at that time. There's nothing more annoying than an application that gets amnesia if the process exits abnormally and settings don't get saved (cough...explorer...cough...)
Then just check to make sure the WindowState == Normal before saving the settings. Obviously its pointless to save the position of a minimized or maximized window.
As for when to load the settings, well that you can just do in the constructor after the InitializeComponent call or you can use the Initialized event. No real reason to use the SourceInitialized event unless you are doing something with the HWND directly which shouldn't be necessary.
Use the WindowInteropHelper object to get the window handle and use Screen.FromHandle method to get the actual screen the window is on. When saving make sure to also save the screen bounds just in case it does not exist any more.
One caveat when restoring the screen to its former state is it has to be done after the window handle is created so can't do it in the constructor else won't work properly in multiple monitor situations. Try doing it on the SourceInitialized callback
Are you doing this via databinding? That is the way I do my window sizing and position. I typically have a UserConfig.xml file that is saved in the Users Profile. Then I create elements in there as I databind them in the program. I have the Application.xaml resource dictionary refer to that file, and all of the settings I want set to XPaths inf the XML. Then I just save the in memory xml document on exit. Only one event to handle, no mess, no fuss.
And you can expand it to encompass as many settings as you like in regard to the UI. Adding plumbing settings is a little more difficult, but not terribly so.
I have a solution for saving Size and State, you can extend it to also save the Position. It's done using a Behavior. Simply Binding the Width and Height did not work as expected, because it would overwrite the "Normal" state's size with the maximized sizes. That's why there are some extra checks like if(state == normal)
There is a Config Property on my Window's DataContext.
You'll need a reference to System.Windows.Interactivity to do that.
public class MainWindowSaveStateBehavior : Behavior<Window>
{
public Config Config
{
get { return (Config)GetValue(ConfigProperty); }
set { SetValue(ConfigProperty, value); }
}
public static readonly DependencyProperty ConfigProperty =
DependencyProperty.Register("Config", typeof(Config), typeof(MainWindowSaveStateBehavior), new PropertyMetadata(Config_Changed));
private static void Config_Changed(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var b = d as MainWindowSaveStateBehavior;
if(e.NewValue != null) b.LoadSettings();
}
protected override void OnAttached()
{
base.OnAttached();
AssociatedObject.SizeChanged += Window_SizeChanged;
AssociatedObject.StateChanged += Window_StateChanged;
LoadSettings();
}
bool _initialized = false;
private void Window_StateChanged(object sender, EventArgs e)
{
SaveSettings();
}
private void Window_SizeChanged(object sender, SizeChangedEventArgs e)
{
SaveSettings();
}
private void LoadSettings()
{
if (Config == null) return;
AssociatedObject.Width = Config.WindowWidth;
AssociatedObject.Height = Config.WindowHeight;
AssociatedObject.WindowState = Config.WindowState;
_initialized = true;
}
private void SaveSettings()
{
if (Config == null || !_initialized) return;
Config.WindowState = AssociatedObject.WindowState;
if(AssociatedObject.WindowState == WindowState.Normal)
{
Config.WindowWidth = AssociatedObject.Width;
Config.WindowHeight = AssociatedObject.Height;
}
}
}
In Xaml use the behavior by adding the namespaces
xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
xmlns:b="<the namespace your behavior lives in>"
And then attach the Behavior
<i:Interaction.Behaviors>
<b:MainWindowSaveStateBehavior Config="{Binding Config}" />
</i:Interaction.Behaviors>
You then just have to Load and Save the Config in your DataContext on startup/shutdown.
I liked CodeWarriors answer. I use TwoWay binding to apps settings:
Height="{Binding Source={x:Static p:Settings.Default}, Path=WindowHeight, Mode=TwoWay}"
Width="{Binding Source={x:Static p:Settings.Default}, Path=WindowWidth, Mode=TwoWay}"
Top="{Binding Source={x:Static p:Settings.Default}, Path=WindowTop, Mode=TwoWay}"
Left="{Binding Source={x:Static p:Settings.Default}, Path=WindowLeft, Mode=TwoWay}"
where p - project's properties namespace.