I am using a custom WindowsFormsHost
control to wrap FastColoredTextbox winforms control in a tab control whose tabs are dynamically created when user hits the new document button via a DataTemplate
.
Because of the limitations of the tab control which is explained in this SO question, my Undo/Redo logic was holding the history of all the tabs for each tab, which was resulting in wrong text being displayed.
So I decided to modify the FastColoredTextbox control to expose history and the redo stack, and added them to my custom WindowsFormsHost wrapper control as dependency properties which is bound two ways to my DocumentModel.
XAML:
<TabControl TabStripPlacement="Top" Margin="5" ItemsSource="{Binding Documents}" SelectedItem="{Binding CurrentDocument, Mode=TwoWay}" x:Name="TabDocuments">
<TabControl.ItemTemplate>
<DataTemplate>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="10" MaxWidth="10" MinWidth="10"/>
<ColumnDefinition Width="10" MaxWidth="10" MinWidth="10"/>
</Grid.ColumnDefinitions>
<TextBlock Grid.Column="0" Text="{Binding Metadata.FileName}"/>
<TextBlock Grid.Column="1" Text="*" Visibility="{Binding IsSaved, Converter={StaticResource VisibilityConverter}}"/>
<Button Grid.Column="2" Width="10" Height="10" MaxWidth="10" MaxHeight="10" MinWidth="10" MinHeight="10" VerticalAlignment="Center" HorizontalAlignment="Right" Command="{Binding CloseDocumentButtonCommand}">
<TextBlock Text="X" HorizontalAlignment="Center" VerticalAlignment="Center" FontSize="8" FontWeight="Bold"/>
</Button>
</Grid>
</DataTemplate>
</TabControl.ItemTemplate>
<TabControl.ContentTemplate>
<DataTemplate x:Name="TabDocumentsDataTemplate">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Border BorderBrush="Black" BorderThickness="1" Grid.Column="0" Margin="5">
<customControls:CodeTextboxHost Text="{Binding Markdown, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" History="{Binding History, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" RedoStack="{Binding RedoStack, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" WordWrap="True" x:Name="CodeTextboxHost"/>
</Border>
<Border BorderBrush="Black" BorderThickness="1" Grid.Column="1" Margin="5">
<wpf:ChromiumWebBrowser Address="{Binding Html, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" x:Name="ChromiumWebBrowser"/>
</Border>
</Grid>
</DataTemplate>
</TabControl.ContentTemplate>
</TabControl>
The relevant part of the model:
public ObservableCollection<UndoableCommand> History
{
get { return _history; }
set
{
_history = value;
OnPropertyChanged();
}
}
public ObservableCollection<UndoableCommand> RedoStack
{
get { return _redoStack; }
set
{
_redoStack = value;
OnPropertyChanged();
}
}
The relevant parts of the custom control code:
public static readonly DependencyProperty TextProperty = DependencyProperty.Register("Text", typeof(string), typeof(CodeTextboxHost), new PropertyMetadata("", new PropertyChangedCallback(
(d, e) =>
{
var textBoxHost = d as CodeTextboxHost;
if (textBoxHost != null && textBoxHost._innerTextbox != null)
{
textBoxHost._innerTextbox.Text = textBoxHost.GetValue(e.Property) as string;
}
}), null));
public static readonly DependencyProperty HistoryProperty = DependencyProperty.Register("History", typeof(LimitedStack<UndoableCommand>), typeof(CodeTextboxHost), new PropertyMetadata(new LimitedStack<UndoableCommand>(200), new PropertyChangedCallback(
(d, e) =>
{
var textBoxHost = d as CodeTextboxHost;
if (textBoxHost != null && textBoxHost._innerTextbox != null)
{
var history = textBoxHost.GetValue(e.Property) as LimitedStack<UndoableCommand>;
if (history != null)
{
textBoxHost._innerTextbox.TextSource.Manager.History = history;
textBoxHost._innerTextbox.OnUndoRedoStateChanged();
}
else
{
textBoxHost._innerTextbox.ClearUndo();
}
}
}), null));
public static readonly DependencyProperty RedoStackProperty = DependencyProperty.Register("RedoStack", typeof(Stack<UndoableCommand>), typeof(CodeTextboxHost), new PropertyMetadata(new Stack<UndoableCommand>(), new PropertyChangedCallback(
(d, e) =>
{
var textBoxHost = d as CodeTextboxHost;
if (textBoxHost != null && textBoxHost._innerTextbox != null)
{
var redoStack = textBoxHost.GetValue(e.Property) as Stack<UndoableCommand>;
if (redoStack != null)
{
textBoxHost._innerTextbox.TextSource.Manager.RedoStack = redoStack;
textBoxHost._innerTextbox.OnUndoRedoStateChanged();
}
else
{
textBoxHost._innerTextbox.ClearUndo();
}
}
}), null));
public LimitedStack<UndoableCommand> History
{
get { return (LimitedStack<UndoableCommand>) GetValue(HistoryProperty);}
set { SetValue(HistoryProperty, value);}
}
public Stack<UndoableCommand> RedoStack
{
get { return (Stack<UndoableCommand>) GetValue(RedoStackProperty); }
set { SetValue(RedoStackProperty, value);}
}
public CodeTextboxHost()
{
Child = _innerTextbox;
_innerTextbox.Language = FastColoredTextBoxNS.Language.Custom;
_innerTextbox.DescriptionFile = AppDomain.CurrentDomain.BaseDirectory + "SyntaxConfig\\MarkdownSyntaxHighlighting.xml";
_innerTextbox.HighlightingRangeType = HighlightingRangeType.AllTextRange;
_innerTextbox.TextChanged += _innerTextbox_TextChanged;
}
private void _innerTextbox_TextChanged(object sender, TextChangedEventArgs e)
{
Text = _innerTextbox.Text;
History = _innerTextbox.TextSource.Manager.History;
RedoStack = _innerTextbox.TextSource.Manager.RedoStack;
}
My problem is when TextChanged
event is triggered, History
or RedoStack
does not update on the model while Text
perfectly updates.
I tried adding RelativeSource as this SO question suggests but it didn't work.
Any help/ideas would be appreciated. Thank you.
Edit 1:
As suggested I made all collections ObservableCollection, however it didn't make any change, as the model is again not updated.
public static readonly DependencyProperty TextProperty = DependencyProperty.Register("Text", typeof(string), typeof(CodeTextboxHost), new PropertyMetadata("", new PropertyChangedCallback(
(d, e) =>
{
var textBoxHost = d as CodeTextboxHost;
if (textBoxHost != null && textBoxHost._innerTextbox != null)
{
textBoxHost._innerTextbox.Text = textBoxHost.GetValue(e.Property) as string;
}
}), null));
public static readonly DependencyProperty HistoryProperty = DependencyProperty.Register("History", typeof(ObservableCollection<UndoableCommand>), typeof(CodeTextboxHost), new PropertyMetadata(new ObservableCollection<UndoableCommand>(), new PropertyChangedCallback(
(d, e) =>
{
var textBoxHost = d as CodeTextboxHost;
if (textBoxHost != null && textBoxHost._innerTextbox != null)
{
var history = textBoxHost.GetValue(e.Property) as ObservableCollection<UndoableCommand>;
if (history != null)
{
textBoxHost._innerTextbox.TextSource.Manager.History = history.ToLimitedStack(200);
textBoxHost._innerTextbox.OnUndoRedoStateChanged();
}
else
{
textBoxHost._innerTextbox.ClearUndo();
}
}
}), null));
public static readonly DependencyProperty RedoStackProperty = DependencyProperty.Register("RedoStack", typeof(ObservableCollection<UndoableCommand>), typeof(CodeTextboxHost), new PropertyMetadata(new ObservableCollection<UndoableCommand>(), new PropertyChangedCallback(
(d, e) =>
{
var textBoxHost = d as CodeTextboxHost;
if (textBoxHost != null && textBoxHost._innerTextbox != null)
{
var redoStack = textBoxHost.GetValue(e.Property) as ObservableCollection<UndoableCommand>;
if (redoStack != null)
{
textBoxHost._innerTextbox.TextSource.Manager.RedoStack = redoStack.ToStack();
textBoxHost._innerTextbox.OnUndoRedoStateChanged();
}
else
{
textBoxHost._innerTextbox.ClearUndo();
}
}
}), null));
public ObservableCollection<UndoableCommand> History
{
get { return (ObservableCollection<UndoableCommand>) GetValue(HistoryProperty);}
set { SetValue(HistoryProperty, value);}
}
public ObservableCollection<UndoableCommand> RedoStack
{
get { return (ObservableCollection<UndoableCommand>) GetValue(RedoStackProperty); }
set { SetValue(RedoStackProperty, value);}
}
public string Text
{
get { return (string)GetValue(TextProperty); }
set { SetValue(TextProperty, value); }
}
public bool WordWrap
{
get { return (bool)GetValue(WordWrapProperty); }
set { SetValue(WordWrapProperty, value); }
}
public CodeTextboxHost()
{
Child = _innerTextbox;
_innerTextbox.Language = FastColoredTextBoxNS.Language.Custom;
_innerTextbox.DescriptionFile = AppDomain.CurrentDomain.BaseDirectory + "SyntaxConfig\\MarkdownSyntaxHighlighting.xml";
_innerTextbox.HighlightingRangeType = HighlightingRangeType.AllTextRange;
_innerTextbox.TextChanged += _innerTextbox_TextChanged;
}
private void _innerTextbox_TextChanged(object sender, TextChangedEventArgs e)
{
Text = _innerTextbox.Text;
History = _innerTextbox.TextSource.Manager.History.ToOveObservableCollection();
RedoStack = _innerTextbox.TextSource.Manager.RedoStack.ToObservableCollection();
}