In my MainWindow I have a FlowDocumentScrollViewer binding its property Document to a FlowDocument in my MainViewModel.
This document is loaded from an external xaml file store on a remote computer. Currently I'm able to load this document properly via XamlReader.Load(xamlfile) and display it in the FlowDocumentScrollViewer. So far so good.
The problem occurs when I try to add hyperlink in this document. Because to handle the RequestNavigate event I need a x:Class. For the time being this Class need to be my MainWindow because the event is handle in the code-behind. Obviously when I add x:Class="Ugrader.MainWindow" in my external document I get a lovely 'System.Windows.Markup.XamlParseException' at the moment of parsing.
So is there a way to solve this ?
Here is piece of my code
MainWindow.xaml
<Window x:Class="Ugrader.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Geco3-Upgrading version"
WindowStyle="none" ResizeMode="NoResize" ShowInTaskbar="False" WindowStartupLocation="CenterScreen"
Height="400" Width="700"
DataContext="{Binding Main,Source={StaticResource Locator}}">
<FlowDocumentScrollViewer Grid.Column="1" Background="{x:Null}" VerticalScrollBarVisibility="Hidden"
Document="{Binding WhatsNewDoc}"/>
</Window>
MainViewModel.cs
namespace Ugrader.ViewModel
{
public class MainViewModel : ViewModelBase
{
#region Constructor
public MainViewModel()
{
try
{
FileStream xamlFile = new FileStream(updateLocation + "whatsnew.xaml", FileMode.Open, FileAccess.Read);
FlowDocument current = System.Windows.Markup.XamlReader.Load(xamlFile) as FlowDocument;
WhatsNewDoc = current;
}
catch (Exception)
{
}
}
#endregion
#region Properties
private FlowDocument _watsNewDoc = new FlowDocument();
public FlowDocument WhatsNewDoc
{
get
{
return _watsNewDoc;
}
set
{
if(_watsNewDoc != value)
{
_watsNewDoc = value;
RaisePropertyChanged("WhatsNewDoc");
}
}
}
#endregion
}
}
The external FlowDocument
<FlowDocument x:Class="Ugrader.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
ColumnWidth="400" FontSize="12" FontFamily="Century Gothic" Foreground="LightGray">
<Paragraph>
<Italic>
For additionnal information, please watch this
<Hyperlink TextDecorations="{x:Null}" RequestNavigate="Hyperlink_Clicked" NavigateUri="path_to_the_file" >video</Hyperlink>
</Italic>
</Paragraph>
</FlowDocument>
By the way, is there a way to handle this parse exception (in case of bad external file), because even in this try/catch block, this stop my program.
Thanks you in advance,
Bastien.
I find a way to deal with my problem, it's not really beautiful, do not respect the spirit of mvvm, but well, this is working.
So, as it's not possible to add x:Class at runtime (I guess), I came to the idea of handling the RequestNavigate event of each Hyperlinkat runtime. So the solution is pretty simple (and dirty).
In code-behind (yeah I know, it's ugly), on the MainWindow loaded event, I find all hyperlinks in my document, and handle each RequestNavigate events. As simple (and dirty) as this.
Here is some code :
private void Window_Loaded(object sender, RoutedEventArgs e)
{
var hyperlinks = GetVisuals(this).OfType<Hyperlink>();
foreach (var link in hyperlinks)
link.RequestNavigate += link_RequestNavigate;
}
public static IEnumerable<DependencyObject> GetVisuals(DependencyObject root)
{
foreach (var child in LogicalTreeHelper.GetChildren(root).OfType<DependencyObject>())
{
yield return child;
foreach (var descendants in GetVisuals(child))
yield return descendants;
}
}
If someone has a better solution, I take it.
Related
I have an unsafe class that generate a Bitmap which is converted to ToImageSource in order to draw into a Window. The Bitmap itself contains a sinusoidal text which is frequently updated and I want to it "move" from the left to the right (marquee style?). Anyway it works just fine in a WinForm but I'm stuck with the WPF Window.
Here are some code samples:
public AboutWindow()
{
InheritanceBehavior = InheritanceBehavior.SkipAllNow;
InitializeComponent();
Initialize();
}
protected override void OnRender(DrawingContext drawingContext)
{
base.OnRender(drawingContext);
drawingContext.DrawImage(bitmapRender.WindowBitmap, drawingArea);
if (!worker.IsBusy)
worker.RunWorkerAsync(); // BackgroundWorker in charge of updating the bitmap
}
void DispatcherTimerRender_Tick(object sender, EventArgs e) => InvalidateVisual();
My issues are: there is nothing displayed on the Window and the DispatchedTimer that calls InvalidateVisual() leads to this exception:
System.InvalidOperationException: 'Cannot use a DependencyObject that belongs to a different thread than its parent Freezable.'
I have looked at other threads and I'm aware that WPF is a retained drawing system but I would love to achieve it anyway.
Any suggestion about the "best" way to achieve this?
Any useful explanation/link would be very much appreciated.
[Edit]
<Window x:Class="CustomerManagement.View.AboutWindow"
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" Height="450" WindowStartupLocation="CenterScreen" Width="800" ResizeMode="NoResize" AllowsTransparency="True" WindowStyle="None">
<Grid KeyDown="Grid_KeyDown">
<Image Width="800" Height="450" Source="{Binding 'Image'}" />
</Grid>
</Window>
You should use an Image element that has its Source property bound to an ImageSource property in a view model. This is the "standard" way, based on the MVVM architectural pattern, and therefore the "best" way - in my opinion.
<Image Source="{Binding Image}"/>
The view model could look like this:
public class ViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private ImageSource image;
public ImageSource Image
{
get { return image; }
set
{
image = value;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(Image)));
}
}
}
and an instance of it would be assigned to the DataContext of the window:
public AboutWindow()
{
InitializeComponent();
var vm = new ViewModel();
DataContext = vm;
}
For testing it, the code below performs a slide show of image files in a directory. You may as well assign any other ImageSource - e.g. a DrawingImage - to the Image property.
var imageFiles = Directory.GetFiles(..., "*.jpg");
var index = -1;
var timer = new DispatcherTimer { Interval = TimeSpan.FromSeconds(1) };
timer.Tick += (s, e) =>
{
if (++index < imageFiles.Length)
{
vm.Image = new BitmapImage(new Uri(imageFiles[index]));
}
else
{
timer.Stop();
}
};
timer.Start();
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.
I'm currently doing a touch application in WPF. It works great so far but sometimes I need to launch the built-in web browser control. My problem is that despite that the zoom, scroll and such are working, I can't get the Windows 8 virtual keyboard (like in IE 11) to show up when the user gets the focus on web text inputs.
Is there any way to achieve such behavior ? Please keep in mind my WPF app is supposed to run topmost and entirely fullscreen all the time so I can't ask the user to bring up the virtual keyboard manually.
Finally found it here... As written, the Winforms WebBrowser has better wrapper for HTMLDocument, making it way easier than using MSHTML interop. Here's a code snippet :
C# :
public partial class BrowserWindow : Window
{
public BrowserWindow(string url)
{
InitializeComponent();
WebView.ScriptErrorsSuppressed = true;
WebView.AllowNavigation = true;
WebView.Navigate(new Uri(url));
WebView.DocumentCompleted += LoadCompleteEventHandler;
}
private void LoadCompleteEventHandler(object sender, WebBrowserDocumentCompletedEventArgs navigationEventArgs)
{
HtmlElementCollection elements = this.WebView.Document.GetElementsByTagName("input");
foreach (HtmlElement input in elements)
{
if (input.GetAttribute("type").ToLower() == "text")
{
input.GotFocus += (o, args) => VirtualKeyBoardHelper.AttachTabTip();
input.LostFocus += (o, args) => VirtualKeyBoardHelper.RemoveTabTip();
}
}
}
}
XAML :
<Window
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:wf="clr-namespace:System.Windows.Forms;assembly=System.Windows.Forms"
x:Class="PlayPlatform.BrowserWindow"
Title="Browser" ResizeMode="NoResize"
WindowStartupLocation="CenterScreen" WindowState="Maximized" Topmost="True" ShowInTaskbar="False" WindowStyle="None" AllowDrop="False" AllowsTransparency="False">
<WindowsFormsHost HorizontalAlignment="Stretch" VerticalAlignment="Stretch">
<wf:WebBrowser x:Name="WebView" />
</WindowsFormsHost>
</Window>
The VirtualKeyBoardHelper methods are manuallly launching and terminating tabtip.exe.
There is a DockPanel and when I exercise this code it adds the dock content to the right Dock BUT I can't get it to show the text (i.e. Perform Step1 and Step2 etc. as seen below). I have done so much research, nothing has worked. Thanks in advance for your help.
public void ShowInstructionForm()
{
dragDropForm = new DockContent();
dragDropForm.Name = "Hints";
dragDropForm.TabText = "Hints2";
dragDropForm.ShowHint = DockState.DockRight;
dragDropForm.BackColor = Color.White;
dragDropForm.Text = "- Perform the step number 1 ."
+ Environment.NewLine + " - Perform the Step number 2";
try
{
dragDropForm.Show(this.oDock.MainDock);
}
catch (Exception e)
{
MessageBox.Show(this.oDock.MainDock, "error happened " + e.Message);
}
}
Personally I would use data binding to achieve what it is you want so that you are adhering to a more rigid design pattern.
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="350" Width="525"
DataContext="{Binding RelativeSource={RelativeSource Self}}">
<DockPanel>
<TextBlock Text="{Binding Path=Foo}" />
</DockPanel>
</Window>
C#
public partial class MainWindow : Window
{
public string Foo { get; set; }
public MainWindow()
{
Foo = "hello world"; // Changing Foo 'automagically' changes your textblock value
InitializeComponent();
}
}
This allows you to be more flexible by having your business logic separated from your UI code. Obviously this is just an example of data binding with a text block inside a dock panel but hopefully this gives you a better understanding.
Can you go into xaml and place a textblock in in the DockPanel like this:
<DockPanel>
<TextBlock Text="- Perform the step number 1 ."/>
</DockPanel>
I have a popup with StaysOpen=False so I want to close it by clicking anywhere outside of popup. Inside a popup I have a DataGrid. If I open popup and then click somewhere else the popup will be closed. But it won't happen if before clicking outside of popup I will click on column header in DataGrid. Test XAML:
<Window x:Class="Test.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525" VerticalAlignment="Center" HorizontalAlignment="Center" Foreground="Black">
<Grid>
<ToggleButton x:Name="btn" VerticalAlignment="Top">Open</ToggleButton>
<Popup StaysOpen="False" IsOpen="{Binding IsChecked, ElementName=btn}" >
<DataGrid Width="150" Height="150">
<DataGrid.Columns>
<DataGridTextColumn Header="Column" />
</DataGrid.Columns>
</DataGrid>
</Popup>
</Grid>
</Window>
I think that it happens because column header captures the mouse on click and popup doesn't receive mouse events anymore. I've tried to add a handler on LostMouseCapture event in order to capture mouse back by popup but it doesn't seem to work that easy. Any ideas?
Maybe it will help.
Attached behavior:
public class DataGridColumnHeaderReleaseMouseCaptureBehavior {
public static DataGrid GetReleaseDGCHeaderBehavior(DependencyObject obj) {
return (DataGrid)obj.GetValue(ReleaseDGCHeaderBehaviorProperty);
}
public static void SetReleaseDGCHeaderBehavior(DependencyObject obj, Boolean value) {
obj.SetValue(ReleaseDGCHeaderBehaviorProperty, value);
}
public static readonly DependencyProperty ReleaseDGCHeaderBehaviorProperty =
DependencyProperty.RegisterAttached("ReleaseDGCHeaderBehavior",
typeof(DataGrid),
typeof(DataGridColumnHeaderReleaseMouseCaptureBehavior),
new UIPropertyMetadata(default(DataGrid), OnReleaseDGCHeaderBehaviorPropertyChanged));
private static Popup _popup;
private static void OnReleaseDGCHeaderBehaviorPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) {
var oldGrid = (DataGrid)e.OldValue;
if (oldGrid != null)
oldGrid.MouseLeave -= OnMouseLeave;
var refSender = d as Popup;
_popup = refSender;
if (refSender != null) {
var refGrid = e.NewValue as DataGrid;
if (refGrid != null) {
refGrid.MouseLeave += OnMouseLeave;
}
}
}
static void OnMouseLeave(object sender, MouseEventArgs args) {
if (_popup != null)
typeof(Popup).GetMethod("EstablishPopupCapture", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance).Invoke(_popup, null);
}
}
XAML:
<Popup x:Name="popup"
bhvrs:DataGridColumnHeaderReleaseMouseCaptureBehavior.ReleaseDGCHeaderBehavior="{Binding ElementName=dataGrid}">
<DataGrid x:Name="dataGrid"/>
</Popup>
I think you've stumbled onto just a plain old bug. I've reproduced this and could not find a reasonable way to get it working. I think you should file a bug with Microsoft. It seems like a component that captures the mouse and the uncaptures it doesn't restore the capture to the originally capturing component.
I had a similar problem recently altough not exactly the same, and it was in Silverlight. I hacked my way through it by searching the required control (in your case the popup I guess) with the GetTemplatedParent function, in the required event handler of the 'misbehaving' control, and programatically do what I wanted to do on it.
This is not a nice solution, and doesn't solve all the problems, but you can give it a try. Be sure you comment what have you done, because it can turn into a mess.
i had the same problem,and did something like this:
private void YourDataGrid_PreviewMouseLeftButtonUp(object sender, MouseButtonEventArgs e)
{
YourDataGrid.CaptureMouse();
YourDataGrid.ReleaseMouseCapture();
}
but i'm looking for something better yet...