So I want the user to change the displayed window by dragging it sideways. It shouldn't matter where exactly on the screen is his cursor (so only the dragging action matter) I draw a little representation of what I have in mind. I want the user to see the 2nd screen coming after the cursor, even retracting when he moves his cursor back to the right (basically following his cursor). This action should be both ways: from main window to 2nd window AND from 2nd window back to main window
How would you approach this?
EDIT:
Picture 1; The user places his cursor on point A and clicks. While holding click he drags it across the green arrow.
Picture2; This picture represents and intermediate state when the "slide" is still in progress (you can see the "Statistics" screen still hasn't fully taken over the initial screen)
Picture3; Represents the final state; after the user got at the end of the (imaginary) green arrow. The Statistics screen is now fully displayed and the user can read the information that is on it.
The opposite of what happened now should be allowed (dragging from left to right in order to go back to the initial screen)
You need to apply a translate transform to your content.
The following example shows how to drag the content (or the image of the content). For simplicity, the example only shows how to swipe from right to left. It also doesn't show how to implement a history to navigate back. You would need a Queue to store the next pages and a Stack for the previous pages (based on the swipe direction).
Although the below example does it, I don't recommend to use controls directly to handle pages and their navigation. Instead create page data models and render the controls using a DataTemplate for each page model.
MainWindow.xaml.cs
public partial class MainWindow : Window
{
public object CurrentPage
{
get => (object)GetValue(CurrentPageProperty);
set => SetValue(CurrentPageProperty, value);
}
public static readonly DependencyProperty CurrentPageProperty = DependencyProperty.Register(
"CurrentPage",
typeof(object),
typeof(MainWindow),
new PropertyMetadata(default));
private StackPanel DraggableContent { get; }
// Accept swipe after 25% swipe delta
// based on the max possible swipe width (a displayed page).
// Note that the 'PageHost' will host two pages (their image)
// during the swipe action.
private double SwipeAcceptDragThreshold
=> this.PageHost.ActualWidth / 2 * 0.25;
private bool IsSwiping { get; set; }
private double HorizontalDragStart { get; set; }
private UserControl PreviousPage { get; set; }
private UserControl NextPage { get; set; }
/* Page controls */
private UserControl GreenPage { get; }
private UserControl OrangePage { get; }
private UserControl BluePage { get; }
public MainWindow()
{
InitializeComponent();
this.GreenPage = new PageControl() { Background = Brushes.Green };
this.OrangePage = new PageControl() { Background = Brushes.Orange };
this.BluePage = new GreenPage() { Background = Brushes.Blue };
this.CurrentPage = this.GreenPage;
this.NextPage = this.OrangePage;
this.DraggableContent = new StackPanel() { Orientation = Orientation.Horizontal };
}
protected override void OnPreviewMouseLeftButtonDown(MouseButtonEventArgs e)
{
base.OnPreviewMouseLeftButtonDown(e);
this.HorizontalDragStart = e.GetPosition(this).X;
}
protected override void OnPreviewMouseMove(MouseEventArgs e)
{
base.OnPreviewMouseMove(e);
if (e.LeftButton == MouseButtonState.Released)
{
return;
}
double dragDelta = e.GetPosition(this).X - this.HorizontalDragStart;
if (!this.IsSwiping)
{
bool isSwipeRightToLeft = dragDelta < HorizontalDragStart;
UserControl swipeInPage = null;
UserControl swipeOutPage = null;
if (isSwipeRightToLeft)
{
if (this.NextPage == null)
{
return;
}
swipeInPage = this.NextPage;
swipeOutPage = this.CurrentPage as UserControl;
this.PreviousPage = this.CurrentPage as UserControl;
}
this.IsSwiping = true;
/* Create an image of the content that will be dragged, using VisualBrush */
swipeInPage.Height = this.PageHost.ActualHeight;
swipeInPage.Width = this.PageHost.ActualWidth;
swipeOutPage.Height = this.PageHost.ActualHeight;
swipeOutPage.Width = this.PageHost.ActualWidth;
this.CurrentPage = null;
// Prepare the snapshot
this.DraggableContent.Children.Add(swipeOutPage);
this.DraggableContent.Children.Add(swipeInPage);
// To improve performance, the user will only drag a snapshot
// of the pages. The snapshot is painted on an empty Grid
// using a VisualBrush.
this.CurrentPage = new Grid()
{
Background = new VisualBrush(DraggableContent),
Width = this.PageHost.ActualWidth * 2 // Host two pages
};
}
this.TranslateTransform.X = dragDelta;
}
protected override void OnMouseLeftButtonUp(MouseButtonEventArgs e)
{
base.OnMouseLeftButtonUp(e);
if (this.IsSwiping)
{
this.IsSwiping = false;
double dragDelta = Math.Abs(e.GetPosition(this).X - this.HorizontalDragStart);
bool isDragAccepted = dragDelta > this.SwipeAcceptDragThreshold;
// Disconnect page controls from the visual tree
this.DraggableContent.Children.Clear();
this.CurrentPage = isDragAccepted
? NextPage
: this.PreviousPage;
this.TranslateTransform.X = 0;
// TODO::Generate and set next page or null if last page reached
this.NextPage = this.BluePage;
}
}
}
MainWindow.xaml
<Window>
<!-- Allow content to exceed the Window -->
<ScrollViewer HorizontalScrollBarVisibility="Hidden"
VerticalScrollBarVisibility="Hidden">
<ContentPresenter x:Name="PageHost"
Content="{Binding RelativeSource={RelativeSource AncestorType=Window}, Path=CurrentPage}">
<ContentPresenter.RenderTransform>
<TranslateTransform x:Name="TranslateTransform" />
</ContentPresenter.RenderTransform>
</ContentPresenter>
</ScrollViewer>
</Window>
PageControl.xaml
<UserControl>
<Grid Background="{Binding RelativeSource={RelativeSource AncestorType=UserControl}, Path=Background}" />
</UserControl>
Okay guys, I have been scratching my head like mad over this issue and have spent a good few hours trying to research how it works but I am yet to find an answer, if you wish to see any of my SRC feel free to ask about it and I will see if I can help.
Basically the issue I am having is that I have a TreeView of folders in my application i.e.:
Catalog
Brands
Nike
Adidas
Lactose
Styles
Sandles
Trainers
Boots
The issue that I am trying to fix is that when I drag a folder around (This is handled in my DragDropManager class), I am unable to scroll up or down(simply displays a lovely stop sign). I am also unable to find a scroller actually within the TreeView, so I am unsure how it is being generated (This is not my own software, I have recently started working for a company so I am not familiar with the code and no one else seems to know.)
This is a problem if I want to move something from the very top to the very bottom.
The scrolling works fine on its own without the dragging being done.
If anyone wishes to see any part of my code feel free to ask as I am unsure what to actually show you guys.
I have read through a good few articles and am just left scratching my head.
I have created an attached property for achieving this behavior, have a look at my post here -
Attached Behavior for auto scrolling containers while doing Drag & Drop
Main logic is something like this -
private static void OnContainerPreviewDragOver(object sender, DragEventArgs e)
{
FrameworkElement container = sender as FrameworkElement;
if (container == null) { return; }
ScrollViewer scrollViewer = GetFirstVisualChild<ScrollViewer>(container);
if (scrollViewer == null) { return; }
double tolerance = 60;
double verticalPos = e.GetPosition(container).Y;
double offset = 20;
if (verticalPos < tolerance) // Top of visible list?
{
//Scroll up
scrollViewer.ScrollToVerticalOffset(scrollViewer.VerticalOffset - offset);
}
else if (verticalPos > container.ActualHeight - tolerance) //Bottom of visible list?
{
//Scroll down
scrollViewer.ScrollToVerticalOffset(scrollViewer.VerticalOffset + offset);
}
}
Similar questions on SO (although they are mostly for ListBox/ListView but should work for TreeView too) -
WPF Listbox auto scroll while dragging
WPF ListView Databound Drag/Drop Auto Scroll
WPF Drag-to-scroll doesn't work correctly
I know this question really old, but here is the MVVM way as attached property:
using System.Windows;
using System.Windows.Controls;
namespace AndroidCtrlUI.XTools.Behaviors
{
///<summary>
/// TreeItemAttach
///<para/> TreeViewItem
///</summary>
public sealed class TreeItemAttach
{
#region BringIntoView
///<summary>
/// DependencyProperty
///</summary>
public static readonly DependencyProperty BringIntoViewProperty = DependencyProperty.RegisterAttached("BringIntoView", typeof(bool), typeof(TreeItemAttach), new UIPropertyMetadata(false, (s, e) =>
{
if ((bool)e.NewValue != (bool)e.OldValue && s is TreeViewItem t)
{
if ((bool)e.NewValue)
{
t.Selected += BringIntoView;
}
else
{
t.Selected -= BringIntoView;
}
}
}));
///<summary>
/// Get
///</summary>
///<param name="target">DependencyObject</param>
///<returns>ICommand</returns>
public static bool GetBringIntoView(DependencyObject target)
{
return (bool)target.GetValue(BringIntoViewProperty);
}
///<summary>
/// Set
///</summary>
///<param name="target">DependencyObject</param>
///<param name="value">ICommand</param>
public static void SetBringIntoView(DependencyObject target, bool value)
{
target.SetValue(BringIntoViewProperty, value);
}
private static void BringIntoView(object sender, RoutedEventArgs e)
{
if (e.Source is TreeViewItem s)
{
double h = s.ActualHeight;
if (s.IsExpanded && s.Items.Count > 0)
{
h = s.ActualHeight / TreeWalker(s);
}
s.BringIntoView(new Rect(0, h * -1, s.ActualWidth, h * 2.5));
}
}
private static long TreeWalker(TreeViewItem item)
{
long c = item.Items.Count;
foreach (object i in item.Items)
{
if (i != null && item.ItemContainerGenerator.ContainerFromItem(i) is TreeViewItem t && t.IsExpanded && t.Items.Count > 0)
{
c += TreeWalker(t);
}
}
return c;
}
#endregion
}
}
And it can be used like:
<Style x:Key="TreeViewItemStyle" TargetType="{x:Type TreeViewItem}">
<Setter Property="tool:TreeItemAttach.BringIntoView" Value="True"/>
</Style>
I am making a Silverlight 4 website with C#. In one of the pages, I want to have two panels beside each other. On the left is a map control and on the right is an image. That is easy enough, but I also want to be able to click on the image and leave PushPin like objects (like the map) so I put the image in a canvas and just draw circles. The problem is that image can be fairly large and I need to be able to scroll the image. I tried several different ways of achieving this, but so far no luck.
The answers to the following post seemed to be like the way to go, but there must be updates to Silverlight that broke it: WPF: How to make canvas auto-resize?
A similar solution suggested making the Canvas from scratch, but I ran into the same problem.
Most of my attempts end in displaying as much of the image as possible on the screen, but no scroll bars (still greyed out) or the page just goes white when the image is loaded.
The following is how I am currently selecting the image to load:
OpenFileDialog dialog = new OpenFileDialog();
dialog.Filter = "Image Files (*.png, *.jpg)|*.jpg;*.png";
if(dialog.ShowDialog() == true) {
BitmapImage bitmap = new BitmapImage();
FileStream stream = dialog.File.OpenRead();
bitmap.SetSource(stream);
TheImage.Source = bitmap;
}
There is probably be a nicer solution but this should do the trick.
I have created a small fixed size ScrollViewer that contains a Canvas and an image. I then used a behaviour to modify the size of the canvas to match the size of the image. The behaviour also handles the ImageOpened event to set the correct size of the image once the image is opened.
Here is the xaml:
<ScrollViewer Width="200" Height="200" HorizontalScrollBarVisibility="Auto">
<Canvas x:Name="TheCanvas">
<Image x:Name="TheImage">
<i:Interaction.Behaviors>
<Views:ResizeCanvasBehaviour Canvas="{Binding ElementName=TheCanvas}"/>
</i:Interaction.Behaviors>
</Image>
</Canvas>
</ScrollViewer>
Be sure to declare i as xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"
and b matches the namespace where you place the behaviour.
Here is the code for the behaviour:
public class ResizeCanvasBehaviour : Behavior<Image>
{
protected override void OnAttached()
{
base.OnAttached();
AssociatedObject.SizeChanged += AssociatedObject_SizeChanged;
AssociatedObject.ImageOpened += AssociatedObject_ImageOpened;
}
protected override void OnDetaching()
{
base.OnDetaching();
AssociatedObject.SizeChanged -= AssociatedObject_SizeChanged;
AssociatedObject.ImageOpened -= AssociatedObject_ImageOpened;
}
private void AssociatedObject_ImageOpened(object sender, RoutedEventArgs e)
{
BitmapSource bitmapSource = AssociatedObject.Source as BitmapSource;
if (bitmapSource == null)
{
return;
}
AssociatedObject.Width = bitmapSource.PixelWidth;
AssociatedObject.Height = bitmapSource.PixelHeight;
Resize();
}
private void AssociatedObject_SizeChanged(object sender, SizeChangedEventArgs e)
{
Resize();
}
public Canvas Canvas
{
get { return GetValue(CanvasProperty) as Canvas; }
set { SetValue(CanvasProperty, value); }
}
public static readonly DependencyProperty CanvasProperty = DependencyProperty.Register(
"Canvas",
typeof(Canvas),
typeof(ResizeCanvasBehaviour),
new PropertyMetadata(null, CanvasPropertyChanged));
private static void CanvasPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
((ResizeCanvasBehaviour)d).OnCanvasPropertyChanged();
}
private void OnCanvasPropertyChanged()
{
if (Canvas != null)
{
Resize();
}
}
private void Resize()
{
if ((AssociatedObject != null) && (Canvas != null))
{
Canvas.Width = AssociatedObject.ActualWidth;
Canvas.Height = AssociatedObject.ActualHeight;
}
}
}
To load the image do something like this. I did this in code behind for speed but ideally you should put this in a view model and then data bind the image Source property in xaml:
BitmapImage bi = new BitmapImage();
bi.UriSource = new Uri("http://farm7.static.flickr.com/6149/5942401995_a5a3fd3919_z.jpg");
TheImage.Source = bi;
Turns out the very minimum that I needed to do was set the Width and Height of the canvas to the PixelWidth and PixelHeight of the BitmapImage instance.
This is what Paul was doing with his solution (in a little more complicated way), but for some reason the resize event handlers would not get called when a image was loaded locally.
I had tried several different suggested solutions, but I never got the results I was wanting. This was the only solution that seemed to work.
To do DataBinding of the Document in a WPF RichtextBox, I saw 2 solutions so far, which are to derive from the RichtextBox and add a DependencyProperty, and also the solution with a "proxy".
Neither the first or the second are satisfactory. Does somebody know another solution, or instead, a commercial RTF control which is capable of DataBinding? The normal TextBox is not an alternative, since we need text formatting.
Any idea?
There is a much easier way!
You can easily create an attached DocumentXaml (or DocumentRTF) property which will allow you to bind the RichTextBox's document. It is used like this, where Autobiography is a string property in your data model:
<TextBox Text="{Binding FirstName}" />
<TextBox Text="{Binding LastName}" />
<RichTextBox local:RichTextBoxHelper.DocumentXaml="{Binding Autobiography}" />
Voila! Fully bindable RichTextBox data!
The implementation of this property is quite simple: When the property is set, load the XAML (or RTF) into a new FlowDocument. When the FlowDocument changes, update the property value.
This code should do the trick:
using System.IO;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
public class RichTextBoxHelper : DependencyObject
{
public static string GetDocumentXaml(DependencyObject obj)
{
return (string)obj.GetValue(DocumentXamlProperty);
}
public static void SetDocumentXaml(DependencyObject obj, string value)
{
obj.SetValue(DocumentXamlProperty, value);
}
public static readonly DependencyProperty DocumentXamlProperty =
DependencyProperty.RegisterAttached(
"DocumentXaml",
typeof(string),
typeof(RichTextBoxHelper),
new FrameworkPropertyMetadata
{
BindsTwoWayByDefault = true,
PropertyChangedCallback = (obj, e) =>
{
var richTextBox = (RichTextBox)obj;
// Parse the XAML to a document (or use XamlReader.Parse())
var xaml = GetDocumentXaml(richTextBox);
var doc = new FlowDocument();
var range = new TextRange(doc.ContentStart, doc.ContentEnd);
range.Load(new MemoryStream(Encoding.UTF8.GetBytes(xaml)),
DataFormats.Xaml);
// Set the document
richTextBox.Document = doc;
// When the document changes update the source
range.Changed += (obj2, e2) =>
{
if (richTextBox.Document == doc)
{
MemoryStream buffer = new MemoryStream();
range.Save(buffer, DataFormats.Xaml);
SetDocumentXaml(richTextBox,
Encoding.UTF8.GetString(buffer.ToArray()));
}
};
}
});
}
The same code could be used for TextFormats.RTF or TextFormats.XamlPackage. For XamlPackage you would have a property of type byte[] instead of string.
The XamlPackage format has several advantages over plain XAML, especially the ability to include resources such as images, and it is more flexible and easier to work with than RTF.
It is hard to believe this question sat for 15 months without anyone pointing out the easy way to do this.
I know this is an old post, but check out the Extended WPF Toolkit. It has a RichTextBox that supports what you are tryign to do.
I can give you an ok solution and you can go with it, but before I do I'm going to try to explain why Document is not a DependencyProperty to begin with.
During the lifetime of a RichTextBox control, the Document property generally doesn't change. The RichTextBox is initialized with a FlowDocument. That document is displayed, can be edited and mangled in many ways, but the underlying value of the Document property remains that one instance of the FlowDocument. Therefore, there is really no reason it should be a DependencyProperty, ie, Bindable. If you have multiple locations that reference this FlowDocument, you only need the reference once. Since it is the same instance everywhere, the changes will be accessible to everyone.
I don't think FlowDocument supports document change notifications, though I am not sure.
That being said, here's a solution. Before you start, since RichTextBox doesn't implement INotifyPropertyChanged and Document is not a DependencyProperty, we have no notifications when the RichTextBox's Document property changes, so the binding can only be OneWay.
Create a class that will provide the FlowDocument. Binding requires the existence of a DependencyProperty, so this class inherits from DependencyObject.
class HasDocument : DependencyObject
{
public static readonly DependencyProperty DocumentProperty =
DependencyProperty.Register("Document",
typeof(FlowDocument),
typeof(HasDocument),
new PropertyMetadata(new PropertyChangedCallback(DocumentChanged)));
private static void DocumentChanged(DependencyObject obj, DependencyPropertyChangedEventArgs e)
{
Debug.WriteLine("Document has changed");
}
public FlowDocument Document
{
get { return GetValue(DocumentProperty) as FlowDocument; }
set { SetValue(DocumentProperty, value); }
}
}
Create a Window with a rich text box in XAML.
<Window x:Class="samples.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Flow Document Binding" Height="300" Width="300"
>
<Grid>
<RichTextBox Name="richTextBox" />
</Grid>
</Window>
Give the Window a field of type HasDocument.
HasDocument hasDocument;
Window constructor should create the binding.
hasDocument = new HasDocument();
InitializeComponent();
Binding b = new Binding("Document");
b.Source = richTextBox;
b.Mode = BindingMode.OneWay;
BindingOperations.SetBinding(hasDocument, HasDocument.DocumentProperty, b);
If you want to be able to declare the binding in XAML, you would have to make your HasDocument class derive from FrameworkElement so that it can be inserted into the logical tree.
Now, if you were to change the Document property on HasDocument, the rich text box's Document will also change.
FlowDocument d = new FlowDocument();
Paragraph g = new Paragraph();
Run a = new Run();
a.Text = "I showed this using a binding";
g.Inlines.Add(a);
d.Blocks.Add(g);
hasDocument.Document = d;
I have tuned up previous code a little bit.
First of all range.Changed hasn't work for me.
After I changed range.Changed to richTextBox.TextChanged it turns out that TextChanged event handler can invoke SetDocumentXaml recursively, so I've provided protection against it. I also used XamlReader/XamlWriter instead of TextRange.
public class RichTextBoxHelper : DependencyObject
{
private static HashSet<Thread> _recursionProtection = new HashSet<Thread>();
public static string GetDocumentXaml(DependencyObject obj)
{
return (string)obj.GetValue(DocumentXamlProperty);
}
public static void SetDocumentXaml(DependencyObject obj, string value)
{
_recursionProtection.Add(Thread.CurrentThread);
obj.SetValue(DocumentXamlProperty, value);
_recursionProtection.Remove(Thread.CurrentThread);
}
public static readonly DependencyProperty DocumentXamlProperty = DependencyProperty.RegisterAttached(
"DocumentXaml",
typeof(string),
typeof(RichTextBoxHelper),
new FrameworkPropertyMetadata(
"",
FrameworkPropertyMetadataOptions.AffectsRender | FrameworkPropertyMetadataOptions.BindsTwoWayByDefault,
(obj, e) => {
if (_recursionProtection.Contains(Thread.CurrentThread))
return;
var richTextBox = (RichTextBox)obj;
// Parse the XAML to a document (or use XamlReader.Parse())
try
{
var stream = new MemoryStream(Encoding.UTF8.GetBytes(GetDocumentXaml(richTextBox)));
var doc = (FlowDocument)XamlReader.Load(stream);
// Set the document
richTextBox.Document = doc;
}
catch (Exception)
{
richTextBox.Document = new FlowDocument();
}
// When the document changes update the source
richTextBox.TextChanged += (obj2, e2) =>
{
RichTextBox richTextBox2 = obj2 as RichTextBox;
if (richTextBox2 != null)
{
SetDocumentXaml(richTextBox, XamlWriter.Save(richTextBox2.Document));
}
};
}
)
);
}
<RichTextBox>
<FlowDocument PageHeight="180">
<Paragraph>
<Run Text="{Binding Text, Mode=TwoWay}"/>
</Paragraph>
</FlowDocument>
</RichTextBox>
This seems to be the easiest way by far and isn't displayed in any of these answers.
In the view model just have the Text variable.
Create a UserControl which has a RichTextBox named RTB. Now add the following dependency property:
public FlowDocument Document
{
get { return (FlowDocument)GetValue(DocumentProperty); }
set { SetValue(DocumentProperty, value); }
}
public static readonly DependencyProperty DocumentProperty =
DependencyProperty.Register("Document", typeof(FlowDocument), typeof(RichTextBoxControl), new PropertyMetadata(OnDocumentChanged));
private static void OnDocumentChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
RichTextBoxControl control = (RichTextBoxControl) d;
FlowDocument document = e.NewValue as FlowDocument;
if (document == null)
{
control.RTB.Document = new FlowDocument(); //Document is not amused by null :)
}
else
{
control.RTB.Document = document;
}
}
This solution is probably that "proxy" solution you saw somewhere.. However.. RichTextBox simply does not have Document as DependencyProperty... So you have to do this in another way...
HTH
Most of my needs were satisfied by this answer https://stackoverflow.com/a/2989277/3001007 by krzysztof. But one issue with that code (i faced was), the binding won't work with multiple controls. So I changed _recursionProtection with a Guid based implementation. So it's working for Multiple controls in same window as well.
public class RichTextBoxHelper : DependencyObject
{
private static List<Guid> _recursionProtection = new List<Guid>();
public static string GetDocumentXaml(DependencyObject obj)
{
return (string)obj.GetValue(DocumentXamlProperty);
}
public static void SetDocumentXaml(DependencyObject obj, string value)
{
var fw1 = (FrameworkElement)obj;
if (fw1.Tag == null || (Guid)fw1.Tag == Guid.Empty)
fw1.Tag = Guid.NewGuid();
_recursionProtection.Add((Guid)fw1.Tag);
obj.SetValue(DocumentXamlProperty, value);
_recursionProtection.Remove((Guid)fw1.Tag);
}
public static readonly DependencyProperty DocumentXamlProperty = DependencyProperty.RegisterAttached(
"DocumentXaml",
typeof(string),
typeof(RichTextBoxHelper),
new FrameworkPropertyMetadata(
"",
FrameworkPropertyMetadataOptions.AffectsRender | FrameworkPropertyMetadataOptions.BindsTwoWayByDefault,
(obj, e) =>
{
var richTextBox = (RichTextBox)obj;
if (richTextBox.Tag != null && _recursionProtection.Contains((Guid)richTextBox.Tag))
return;
// Parse the XAML to a document (or use XamlReader.Parse())
try
{
string docXaml = GetDocumentXaml(richTextBox);
var stream = new MemoryStream(Encoding.UTF8.GetBytes(docXaml));
FlowDocument doc;
if (!string.IsNullOrEmpty(docXaml))
{
doc = (FlowDocument)XamlReader.Load(stream);
}
else
{
doc = new FlowDocument();
}
// Set the document
richTextBox.Document = doc;
}
catch (Exception)
{
richTextBox.Document = new FlowDocument();
}
// When the document changes update the source
richTextBox.TextChanged += (obj2, e2) =>
{
RichTextBox richTextBox2 = obj2 as RichTextBox;
if (richTextBox2 != null)
{
SetDocumentXaml(richTextBox, XamlWriter.Save(richTextBox2.Document));
}
};
}
)
);
}
For completeness sake, let me add few more lines from original answer https://stackoverflow.com/a/2641774/3001007 by ray-burns. This is how to use the helper.
<RichTextBox local:RichTextBoxHelper.DocumentXaml="{Binding Autobiography}" />
Here is my solution based on Ray Burns answer with DataBinding and conversion of a XAML-string to a RichTextBox-Document:
ViewModel
TestText = #"<FlowDocument xmlns=""http://schemas.microsoft.com/winfx/2006/xaml/presentation""><Paragraph><Bold>Hello World!</Bold></Paragraph></FlowDocument>";
View
<RichTextBox local:RichTextBoxHelper.DocumentXaml="{Binding TestText}"/>
RichTextBoxHelper
public class RichTextBoxHelper : DependencyObject
{
public static string GetDocumentXaml(DependencyObject obj) { return (string) obj.GetValue(DocumentXamlProperty); }
public static void SetDocumentXaml(DependencyObject obj,
string value)
{
obj.SetValue(DocumentXamlProperty, value);
}
public static readonly DependencyProperty DocumentXamlProperty = DependencyProperty.RegisterAttached
(
"DocumentXaml",
typeof(string),
typeof(RichTextBoxHelper),
new FrameworkPropertyMetadata
{
BindsTwoWayByDefault = true,
PropertyChangedCallback = (obj,
e) =>
{
var richTextBox = (RichTextBox) obj;
var xaml = GetDocumentXaml(richTextBox);
Stream sm = new MemoryStream(Encoding.UTF8.GetBytes(xaml));
richTextBox.Document = (FlowDocument) XamlReader.Load(sm);
sm.Close();
}
}
);
}
Here's a VB.Net version of Lolo's answer:
Public Class RichTextBoxHelper
Inherits DependencyObject
Private Shared _recursionProtection As New HashSet(Of System.Threading.Thread)()
Public Shared Function GetDocumentXaml(ByVal depObj As DependencyObject) As String
Return DirectCast(depObj.GetValue(DocumentXamlProperty), String)
End Function
Public Shared Sub SetDocumentXaml(ByVal depObj As DependencyObject, ByVal value As String)
_recursionProtection.Add(System.Threading.Thread.CurrentThread)
depObj.SetValue(DocumentXamlProperty, value)
_recursionProtection.Remove(System.Threading.Thread.CurrentThread)
End Sub
Public Shared ReadOnly DocumentXamlProperty As DependencyProperty = DependencyProperty.RegisterAttached("DocumentXaml", GetType(String), GetType(RichTextBoxHelper), New FrameworkPropertyMetadata("", FrameworkPropertyMetadataOptions.AffectsRender Or FrameworkPropertyMetadataOptions.BindsTwoWayByDefault, Sub(depObj, e)
RegisterIt(depObj, e)
End Sub))
Private Shared Sub RegisterIt(ByVal depObj As System.Windows.DependencyObject, ByVal e As System.Windows.DependencyPropertyChangedEventArgs)
If _recursionProtection.Contains(System.Threading.Thread.CurrentThread) Then
Return
End If
Dim rtb As RichTextBox = DirectCast(depObj, RichTextBox)
Try
rtb.Document = Markup.XamlReader.Parse(GetDocumentXaml(rtb))
Catch
rtb.Document = New FlowDocument()
End Try
' When the document changes update the source
AddHandler rtb.TextChanged, AddressOf TextChanged
End Sub
Private Shared Sub TextChanged(ByVal sender As Object, ByVal e As TextChangedEventArgs)
Dim rtb As RichTextBox = TryCast(sender, RichTextBox)
If rtb IsNot Nothing Then
SetDocumentXaml(sender, Markup.XamlWriter.Save(rtb.Document))
End If
End Sub
End Class
This VB.Net version works for my situation. I removed thread collection semaphore, instead using RemoveHandler and AddHandler. Also, since a FlowDocument can only be bound to one RichTextBox at a time, I put in a check that the RichTextBox's IsLoaded=True. Let's begin with how I used the class in a MVVM app which uses ResourceDictionary instead of Window.
' Loaded and Unloaded events seems to be the only way to initialize a control created from a Resource Dictionary
' Loading document here because Loaded is the last available event to create a document
Private Sub Rtb_Loaded(ByVal sender As System.Object, ByVal e As System.Windows.RoutedEventArgs)
' only good place to initialize RichTextBox.Document with DependencyProperty
Dim rtb As RichTextBox = DirectCast(sender, RichTextBox)
Try
rtb.Document = RichTextBoxHelper.GetDocumentXaml(rtb)
Catch ex As Exception
Debug.WriteLine("Rtb_Loaded: Message:" & ex.Message)
End Try
End Sub
' Loaded and Unloaded events seems to be the only way to initialize a control created from a Resource Dictionary
' Free document being held by RichTextBox.Document by assigning New FlowDocument to RichTextBox.Document. Otherwise we'll see an of "Document belongs to another RichTextBox"
Private Sub Rtb_Unloaded(ByVal sender As System.Object, ByVal e As System.Windows.RoutedEventArgs)
Dim rtb As RichTextBox = DirectCast(sender, RichTextBox)
Dim fd As New FlowDocument
RichTextBoxHelper.SetDocumentXaml(rtb, fd)
Try
rtb.Document = fd
Catch ex As Exception
Debug.WriteLine("PoemDocument.PoemDocumentView.PoemRtb_Unloaded: Message:" & ex.Message)
End Try
End Sub
Public Class RichTextBoxHelper
Inherits DependencyObject
Public Shared Function GetDocumentXaml(ByVal depObj As DependencyObject) As FlowDocument
Return depObj.GetValue(DocumentXamlProperty)
End Function
Public Shared Sub SetDocumentXaml(ByVal depObj As DependencyObject, ByVal value As FlowDocument)
depObj.SetValue(DocumentXamlProperty, value)
End Sub
Public Shared ReadOnly DocumentXamlProperty As DependencyProperty = DependencyProperty.RegisterAttached("DocumentXaml", GetType(FlowDocument), GetType(RichTextBoxHelper), New FrameworkPropertyMetadata(Nothing, FrameworkPropertyMetadataOptions.AffectsRender Or FrameworkPropertyMetadataOptions.BindsTwoWayByDefault, Sub(depObj, e)
RegisterIt(depObj, e)
End Sub))
Private Shared Sub RegisterIt(ByVal depObj As System.Windows.DependencyObject, ByVal e As System.Windows.DependencyPropertyChangedEventArgs)
Dim rtb As RichTextBox = DirectCast(depObj, RichTextBox)
If rtb.IsLoaded Then
RemoveHandler rtb.TextChanged, AddressOf TextChanged
Try
rtb.Document = GetDocumentXaml(rtb)
Catch ex As Exception
Debug.WriteLine("RichTextBoxHelper.RegisterIt: ex:" & ex.Message)
rtb.Document = New FlowDocument()
End Try
AddHandler rtb.TextChanged, AddressOf TextChanged
Else
Debug.WriteLine("RichTextBoxHelper: Unloaded control ignored:" & rtb.Name)
End If
End Sub
' When a RichTextBox Document changes, update the DependencyProperty so they're in sync.
Private Shared Sub TextChanged(ByVal sender As Object, ByVal e As TextChangedEventArgs)
Dim rtb As RichTextBox = TryCast(sender, RichTextBox)
If rtb IsNot Nothing Then
SetDocumentXaml(sender, rtb.Document)
End If
End Sub
End Class
Guys why bother with all the faff. This works perfectly. No code required
<RichTextBox>
<FlowDocument>
<Paragraph>
<Run Text="{Binding Mytextbinding}"/>
</Paragraph>
</FlowDocument>
</RichTextBox>
Is it possible to programmatically scroll a WPF listview? I know winforms doesn't do it, right?
I am talking about say scrolling 50 units up or down, etc. Not scrolling an entire item height at once.
Yes, you'll have to grab the ScrollViwer from the ListView, or but once you have access to that, you can use the methods exposed by it or override the scrolling. You can also scroll by getting the main content area and using it's implementation of the IScrollInfo interface.
Here's a little helper to get the ScrollViwer component of something like a ListBox, ListView, etc.
public static DependencyObject GetScrollViewer(DependencyObject o)
{
// Return the DependencyObject if it is a ScrollViewer
if (o is ScrollViewer)
{ return o; }
for (int i = 0; i < VisualTreeHelper.GetChildrenCount(o); i++)
{
var child = VisualTreeHelper.GetChild(o, i);
var result = GetScrollViewer(child);
if (result == null)
{
continue;
}
else
{
return result;
}
}
return null;
}
And then you can just use .LineUp() and .LineDown() like this:
private void OnScrollUp(object sender, RoutedEventArgs e)
{
var scrollViwer = GetScrollViewer(uiListView) as ScrollViewer;
if (scrollViwer != null)
{
// Logical Scrolling by Item
// scrollViwer.LineUp();
// Physical Scrolling by Offset
scrollViwer.ScrollToVerticalOffset(scrollViwer.VerticalOffset + 3);
}
}
private void OnScrollDown(object sender, RoutedEventArgs e)
{
var scrollViwer = GetScrollViewer(uiListView) as ScrollViewer;
if (scrollViwer != null)
{
// Logical Scrolling by Item
// scrollViwer.LineDown();
// Physical Scrolling by Offset
scrollViwer.ScrollToVerticalOffset(scrollViwer.VerticalOffset + 3);
}
}
<DockPanel>
<Button DockPanel.Dock="Top"
Content="Scroll Up"
Click="OnScrollUp" />
<Button DockPanel.Dock="Bottom"
Content="Scroll Down"
Click="OnScrollDown" />
<ListView x:Name="uiListView">
<!-- Content -->
</ListView>
</DockPanel>
The Logical scrolling exposed by LineUp and LineDown do still scroll by item, if you want to scroll by a set amount you should use the ScrollToHorizontal/VerticalOffset that I've used above. If you want some more complex scrolling too, then take a look at the answer I've provided in this other question.
Have you tried ScrollIntoView?
Alternatively, if it's not a specific item you brought into view, but an offset from the current position, you can use BringIntoView.