Our tester threw curly brackets at our persisting WPF RichTextBoxes. On save and reopen, there are magically more curly brackets.
I've condensed the issue / code down.
<Window x:Class="WpfApplication1.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Window1" Height="300" Width="300">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="*"></RowDefinition>
<RowDefinition Height="Auto"></RowDefinition>
<RowDefinition Height="*"></RowDefinition>
</Grid.RowDefinitions>
<RichTextBox x:Name="rtb1" />
<Button Grid.Row="1" Click="Button_Click">Draw a fish</Button>
<RichTextBox x:Name="rtb2" Grid.Row="2"/>
</Grid>
</Window>
Two rich text boxes. On button click, the bottom one gets set to the result of the first one after persist and restore.
namespace WpfApplication1
{
/// <summary>
/// Interaction logic for Window1.xaml
/// </summary>
public partial class Window1 : Window
{
public Window1()
{
InitializeComponent();
rtb1.Document = new FlowDocument(new Paragraph(new Run("{")));
}
public static FlowDocument CreateFlowDocumentFromByteArray(byte[] byteArray)
{
return (FlowDocument)XamlReader.Load(new MemoryStream(byteArray));
}
public static byte[] CreateByteArrayFromFlowDocument(FlowDocument flowDocument)
{
MemoryStream mStream = new MemoryStream();
XmlWriterSettings settings = new XmlWriterSettings();
settings.Indent = false;
settings.OmitXmlDeclaration = true;
XamlDesignerSerializationManager dsm = new XamlDesignerSerializationManager(XmlWriter.Create(mStream, settings));
dsm.XamlWriterMode = XamlWriterMode.Value;
XamlWriter.Save(flowDocument, dsm);
mStream.Close();
return mStream.ToArray();
}
private void Button_Click(object sender, RoutedEventArgs e)
{
rtb2.Document = CreateFlowDocumentFromByteArray(CreateByteArrayFromFlowDocument(rtb1.Document));
}
}
}
Why is this happening? How do we stop it?
I'll test the code more thoroughly tonight, but does it happen when the braces are not at the start of the text? E.g., if your run was "Hello{World}" does it still do this?
As you probably know, curly braces are significant in WPF because they are used for markup extensions. The following markup wouldn't work:
<Button Content="{Hello}" />
To get the right output, you'd usually escape it with:
<Button Content="{}{Hello}" />
Since you're using XamlReader.Load, there may be some confusion around the use of {}'s in the XAML, so they are being escaped. But that's just a guess. Out of interest, what does the XAML which is written out look like?
Related
I was recently working on a user control, let's name it TestUserControl, and used two of its instances on one page.
While I was testing, I noticed, that when I type something into the values of upper TestUserControl, go to another page then go back to the first page - the second instance of TestUserControl is filled with values that had been typed into the first one (and even elements which are not part of user control of type TestUserControl are affected!).
Here are the screenshots of described behavior .
And the code of a simple project from which this screenshots come:
Landing page:
<Page x:Class="PageNavigation.Pages.Landing"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:PageNavigation.Pages"
xmlns:controls="clr-namespace:PageNavigation.Controls"
xmlns:n="clr-namespace:PageNavigation"
mc:Ignorable="d"
d:DesignHeight="200"
d:DesignWidth="800"
Title="Landing">
<Grid Background="White"
ButtonBase.Click="Grid_Click">
<WrapPanel Margin="5">
<TextBlock Margin="0 25"
Text="I am a simple text block" />
<n:NavButton Text="Accounts"
ImageSource="/Images/Accounts.png"
NavUri="/Pages/Accounts.xaml" />
<n:NavButton Text="Bills"
ImageSource="/Images/Billing.png"
NavUri="/Pages/Bills.xaml" />
<n:NavButton Text="Employees"
ImageSource="/Images/Employees.png"
NavUri="/Pages/Employees.xaml" />
<n:NavButton Text="Setting"
ImageSource="/Images/Settings.png"
NavUri="/Pages/Setting.xaml" />
<StackPanel Orientation="Horizontal">
<TextBlock Text="User controls:" />
<controls:TestUserControl Width="150"/>
<controls:TestUserControl Width="150"/>
</StackPanel>
</WrapPanel>
</Grid>
</Page>
using System.Windows;
using System.Windows.Controls;
namespace PageNavigation.Pages
{
public partial class Landing : Page
{
public Landing()
{
InitializeComponent();
}
private void Grid_Click(object sender, RoutedEventArgs e)
{
if (e.OriginalSource is not NavButton ClickedButton)
return;
NavigationService.Navigate(ClickedButton.NavUri);
}
}
}
My testing user control:
<UserControl x:Class="PageNavigation.Controls.TestUserControl"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:PageNavigation.Controls"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800">
<StackPanel Margin="5">
<TextBox/>
<TextBox/>
<TextBox/>
<StackPanel Margin="10">
<ComboBox>
<ComboBoxItem>Item 1</ComboBoxItem>
<ComboBoxItem>Item 2</ComboBoxItem>
<ComboBoxItem>Item 3</ComboBoxItem>
</ComboBox>
</StackPanel>
<ToggleButton>Toggle me!</ToggleButton>
</StackPanel>
</UserControl>
using System.Windows.Controls;
namespace PageNavigation.Controls
{
public partial class TestUserControl : UserControl
{
public TestUserControl()
{
InitializeComponent();
}
}
}
Navigation button to another pages (these one with images):
using System;
using System.Windows;
using System.Windows.Controls.Primitives;
using System.Windows.Media;
namespace PageNavigation
{
public class NavButton : ButtonBase
{
static NavButton()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(NavButton), new FrameworkPropertyMetadata(typeof(NavButton)));
}
public static readonly DependencyProperty ImageSourceProperty = DependencyProperty.Register("ImageSource", typeof(ImageSource), typeof(NavButton), new PropertyMetadata(null));
public static readonly DependencyProperty TextProperty = DependencyProperty.Register("Text", typeof(string), typeof(NavButton), new PropertyMetadata(null));
public static readonly DependencyProperty NavUriProperty = DependencyProperty.Register("NavUri", typeof(Uri), typeof(NavButton), new PropertyMetadata(null));
public ImageSource ImageSource
{
get { return (ImageSource)GetValue(ImageSourceProperty); }
set { SetValue(ImageSourceProperty, value); }
}
public string Text
{
get { return (string)GetValue(TextProperty); }
set { SetValue(TextProperty, value); }
}
public Uri NavUri
{
get { return (Uri)GetValue(NavUriProperty); }
set { SetValue(NavUriProperty, value); }
}
}
}
One of example pages which contains back button:
<Page x:Class="PageNavigation.Pages.Employees"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:PageNavigation.Pages"
mc:Ignorable="d"
d:DesignHeight="450"
d:DesignWidth="800"
Title="Employees">
<Grid Background="White">
<Button Content="Back"
Padding="3"
Command="NavigationCommands.BrowseBack"
BorderThickness="0"
HorizontalAlignment="Left"
VerticalAlignment="Top"
Margin="5,5,0,0" />
<Label Content="Employees"
FontSize="50"
HorizontalAlignment="Center"
VerticalAlignment="Center" />
</Grid>
</Page>
using System.Windows.Controls;
namespace PageNavigation.Pages
{
public partial class Employees : Page
{
public Employees()
{
InitializeComponent();
}
}
}
Main window:
<Window x:Class="PageNavigation.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:PageNavigation"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<Grid>
<Frame Source="/Pages/Landing.xaml" NavigationUIVisibility="Hidden"/>
</Grid>
</Window>
using System.Windows;
namespace PageNavigation
{
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
}
}
I'm using for the navigation System.Windows.Navigation.NavigationService and Frame, which seems to be important, as I couldn't reproduce this bug in a simple application that uses switching ContentControl and custom NavigationService.
When I use binding to view model in TestUserControl values, the problem seems to disappear, but what if I do not need binding, because I use elements of control to manage its internal behavior only - using for example toggle button to hide/show content of a text box field?
I was looking for an explanation for this behavior, but could not find any. I was reading about defining user controls, journal in Navigation Service, Data Context, and keeping alive pages when using Frames, but found nothing about user controls magically cloning their values to other controls.
I understand I could miss something simple or I defined all my user controls wrongly, but... this is not a behavior I would expect from a user control created in the simplest possible way.
I would appreciate it if someone could help me and answer my questions:
why is this happening? Is it a bug in WPF itself?
is this possible to create "safe" user control without necessarily using binding its values to an external source - and how to do it properly?
Sorry if I did not provide all the required information, but I don't know which information will be valuable as it seems to be a vague problem. I will try to answer any questions.
It's the frame journal which stores state for pages which have been shown.
If you don't go back to the previous page in your real app you could do:
NavigationService.RemoveBackEntry()
After you navigate.
That should remove whatever the journal just got.
You could also try giving things explicit X:Name and see if that allows the journal mechanism to differentiate.
Personally, I avoid frames and pages and I suggest you might consider using contentpresenter and usercontrols instead. I prefer viewmodel first navigation.
The discovered workaround is to bind values of controls to properties in code behind (it solves the problem) - at least visually.
I am truly reaching here :) (I am a complete newbie to UWP/WinRt)
I am hosting a custom WinRT InkCanvas in a WPF Window. I am completely new to WinRt, so when it comes to loading or saving the inkstroke data, I am lost. The problem is getting/setting
the InkStrokes when they are burried within the InkContainer. I would like to use XAML binding from the WPF window to a backend F# module. (Elmish.wpf).
I currently do have both Elmish.wpf and the WinRT Inkcanvas working in the same application.
(Elmish.wpf runs fine with the new application interface required to host WinRT/UWP controls).
ANY HELP OR GUIDANCE WOULD BE MOST APPRECIATED!
So, not knowing how to get the inkstrokes, this is what I have so far:
My WPF container -- ProgressNoteWindow
Note the use of WindowsXamlHost. Somehow, I need the button go gain access to the inkstrokes contained in the InkContainer of Inkcanvas being hosted by the WindowsXamlHost!
<Window x:Class="Stargate.XI.Client.Views.ProgressNoteWindow"
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:xaml="clr-namespace:Microsoft.Toolkit.Wpf.UI.XamlHost;assembly=Microsoft.Toolkit.Wpf.UI.XamlHost"
xmlns:controls="clr-namespace:Microsoft.Toolkit.Wpf.UI.Controls;assembly=Microsoft.Toolkit.Wpf.UI.Controls"
xmlns:local="clr-namespace:Stargate.XI.Client.Views"
WindowStartupLocation="CenterScreen" WindowState="Maximized"
mc:Ignorable="d"
Title="ProgressNoteWindow" d:DesignHeight="1200" d:DesignWidth="1200">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<xaml:WindowsXamlHost x:Name="MyInkControl" Grid.Row="1" InitialTypeName="Loader2.MyUserControl2" ChildChanged="WindowsXamlHost_ChildChanged" />
<Button Command="{Binding Command}" CommandParameter="{Binding InkStrokes, ElementName=MyInkControl}" Content = "Save"/>
</Grid>
</Window>
My attached property --InkCanvasBinder
public static class InkCanvasBinder
{
public static InkStrokeContainer GetInkStrokes(DependencyObject obj) =>
obj.GetValue(InkStrokesProperty) as InkStrokeContainer;
public static void SetInkStrokes(DependencyObject obj, InkStrokeContainer value) =>
obj.SetValue(InkStrokesProperty, value);
public static DependencyProperty InkStrokesProperty = DependencyProperty.RegisterAttached(
"InkStrokes", typeof(InkStrokeContainer), typeof(InkCanvasBinder),
new PropertyMetadata(null, InkStrokesProperty_PropertyChanged));
private static void InkStrokesProperty_PropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var inkCanvas = d as InkCanvas;
if (inkCanvas != null) inkCanvas.InkPresenter.StrokeContainer = e.NewValue as InkStrokeContainer;
}
}
MyUserControl (in a UWP project):
<UserControl
x:Class="Loader2.MyUserControl2"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:Loader2"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
d:DesignHeight="1200"
d:DesignWidth="1200">
<Grid Background="LightCoral">
<InkCanvas x:Name="TheInkCanvas" Loaded="InkCanvas_OnLoaded" local:InkCanvasBinder.InkStrokes = "{x:Bind MyInkStrokeContainer}"/>
<!-- Ensure the InkToolbar is declared after the InkCanvas. If not, the InkCanvas overlay renders the InkToolbar inaccessible., Mode=OneWay-->
<InkToolbar x:Name="myInkToolbar" TargetInkCanvas="{x:Bind TheInkCanvas}" Width="300" Height="50" Margin="10,10,10,10" HorizontalAlignment="Left" VerticalAlignment="Top" />
</Grid>
</UserControl>
MyUserControl - code-behind (in a UWP project)
using Windows.UI.Core;
using Windows.UI.Input.Inking;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
// The User Control item template is documented at https://go.microsoft.com/fwlink/?LinkId=234236
namespace Loader2
{
public sealed partial class MyUserControl2 : UserControl
{
public MyUserControl2()
{
this.InitializeComponent();
}
private void InkCanvas_OnLoaded(object sender, RoutedEventArgs e)
{
TheInkCanvas.InkPresenter.InputDeviceTypes = CoreInputDeviceTypes.Mouse | CoreInputDeviceTypes.Touch;
}
public InkStrokeContainer MyInkStrokeContainer
{
get { return (InkStrokeContainer)GetValue(MyInkStrokeContainerProperty); }
set { SetValue(MyInkStrokeContainerProperty, value); }
}
// Using a DependencyProperty as the backing store for MyInkStrokeContainer. This enables animation, styling, binding, etc...
public static readonly DependencyProperty MyInkStrokeContainerProperty =
DependencyProperty.Register("MyInkStrokeContainer", typeof(InkStrokeContainer), typeof(MyUserControl2), new PropertyMetadata(null));
}
}
I have a WPF form, which consists of a grid of two columns.
In the left-hand column are the control labels, and in the right-hand column are my controls.
The controls are all UserControls. In the simplest case, some of these controls simply wrap existing WPF controls such as the textbox, so that they all implement a common interface.
When the form is generated, I have code like this to set the label for the associated control, where newControl is the created UserControl and ctl.Caption simply returns the required label text:
Label newLabel = new Label();
newLabel.Content = ctl.Caption + ":";
newLabel.Target = newControl;
One problem is that setting the Target doesn't actually work. If I have an underscore in the caption, the mnemonic key doesn't set focus to the wrapped control. One workaround for this may be to manually set the focus to the wrapped control within the UserControl code - but...
The biggest problem is accessibility. Screenreaders such as JAWS, and Windows built-in Narrator, do not read the control caption when the control receives focus.
I have had a look at this: http://msdn.microsoft.com/en-us/library/windows/desktop/gg712258.aspx - which provides a lot of detail, but no helpful examples. It has a lot of stuff about custom controls, which is surely overkill for a simple user control?
So, how can I "attach" my labels correctly to my UserControls?
You can browse the code for the entire project at http://quest.codeplex.com/SourceControl/changeset/view/676933506953 - the particular code is in the EditorControls project, and the UserControls are instantiated in ElementEditor.xaml.cs.
Your newControl is of type Control that doesn't allow you to add additional content.
If you want to add some content to it you need to use a class that supports it, like ContentControl or Panel (for multiple childs) you can implement you own control that implements the IAddChild interface.
A simple solution for you problem could be :
<UserControl x:Class="UIMocks.MyCustomControl"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<StackPanel x:Name="content"/>
</UserControl>
The code-behind
[ContentProperty("Children")]
public partial class MyCustomControl : UserControl
{
public MyCustomControl()
{
InitializeComponent();
}
public UIElementCollection Children { get { return content.Children; } }
}
and then you can use
MyCustomControl newControl = InitialiseEditorControl(ctl);
...
Label newLabel = new Label();
newLabel.Content = ctl.Caption + ":";
newControl.Children.Add(newLabel);
hmmm I tried to reproduce your issue on a small test project, but for me it works... so I guess you'll have to give more details on how your userControls are built. Here is what works for me:
I created an Empty project (just the App and Window files, as usual) and set up a grid with 2 columns in my window:
<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"
Name="Window"
SizeToContent="WidthAndHeight">
<Grid Name="MyGrid">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="100" />
<ColumnDefinition Width="100"/>
</Grid.ColumnDefinitions>
</Grid>
</Window>
then created a userControl that extends the wpf TextBox class:
<TextBox x:Class="Test.MyTextBox"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300">
</TextBox>
and:
using System.Windows;
using System.Windows.Controls;
namespace Test
{
public partial class MyTextBox : TextBox
{
public static readonly DependencyProperty CaptionProperty =
DependencyProperty.Register("Caption", typeof(string), typeof(MyTextBox), new UIPropertyMetadata(""));
public string Caption
{
get { return (string)GetValue(CaptionProperty); }
set { SetValue(CaptionProperty, value); }
}
public MyTextBox()
{
InitializeComponent();
}
}
}
it's basically a textbox with a "Caption" dp.
and now in My window's code behind:
public MainWindow()
{
InitializeComponent();
MyTextBox tb = new MyTextBox { Caption = "_Foo", Width = 100 };
Label lb = new Label { Content = tb.Caption + ":", Target = tb };
MyGrid.Children.Add(lb);
MyGrid.Children.Add(tb);
Grid.SetColumn(lb, 0);
Grid.SetColumn(tb, 1);
}
and with this, I do get focus on the TB when I press ALT + F (I can even see the _ under the F of "Foo" in the Label when just pressing ALT)
So I guess your issue has to do with your UserControls themselves and how they are built (what Template for instance)
Edit:
If your control is not extending an existing control but rather contains a WPF control, the issue is probably on the Focus method. You should add a Focus() method that sets the focus on the right part of your control when the control itself gets the focus.
code (for a UserControl containing a textbox that you want to get the focus for instance):
<TextBox x:Class="Test.MyTextBox"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition />
<ColumnDefinition />
</Grid.ColumnDefinitions>
<Button Content="foo" Grid.Column="0" />
<TextBox Name="TextBoxPart" Grid.Column="1" />
</Grid>
</TextBox>
code behind
public partial class MyTextBox : TextBox
{
public static readonly DependencyProperty CaptionProperty =
DependencyProperty.Register("Caption", typeof(string), typeof(MyTextBox), new UIPropertyMetadata(""));
public string Caption
{
get { return (string)GetValue(CaptionProperty); }
set { SetValue(CaptionProperty, value); }
}
public MyTextBox()
{
InitializeComponent();
}
protected override void OnGotFocus(RoutedEventArgs e)
{
TextBoxPart.Focus();
}
}
Edit 2:
I had an issue once, to transfer the focus to a subcontrol in a dataGridCell, and here is what I did in the template:
<ControlTemplate.Triggers>
<Trigger Property="IsFocused" Value="True">
<Setter TargetName="TextBoxPart" Property="FocusManager.FocusedElement" Value="{Binding ElementName=TextBoxPart}" />
</Trigger>
</ControlTemplate.Triggers>
you could try adding this to your template. This should transfer your focus alright.
as for the accessibility, I don't think this will help, but I do not see any way of achieving what you want :-/
I have a Window, containing a UserControl 'TemplateEditor'. The (shortened) TemplateEditor XAML is:
<UserControl x:Class="xxx.Windows.Core.Controls.TemplateEditor"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
mc:Ignorable="d"
x:Name="userControl">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<TextBox Grid.Row="1" x:Name="textBox" HorizontalScrollBarVisibility="Auto" VerticalScrollBarVisibility="Auto" />
</Grid>
</UserControl>
I want to be able to bind data through the TemplateEditor into the TextBox "textBox". I'm using a DependencyProperty to mask the TextBox in the code-behind:
namespace xxx.Windows.Core.Controls
{
public partial class TemplateEditor : UserControl
{
public string Text
{
get
{
string s=(string)GetValue(TextProperty);
return s;
}
set
{
if (Text != value)
{
SetValue(TextProperty, value);
}
}
}
public static readonly DependencyProperty TextProperty =
DependencyProperty.Register("Text", typeof(string), typeof(TemplateEditor),new PropertyMetadata(null,Text_PropertyChanged));
public TemplateEditor()
{
InitializeComponent();
}
private static void Text_PropertyChanged(DependencyObject source, DependencyPropertyChangedEventArgs e)
{
((TemplateEditor)source).textBox.Text = (string)e.NewValue;
}
}
}
So from that you can see I have a DependencyProperty Text that I have a callback on to pick up changes made by to the binding (say, from the ViewModel) and apply to the TextBox value.
This works.
The problem is, I can't seem to have the binding work in reverse, ie. to get the value back out of the Text property (and therefore the TextBox) and back in to the binding consumer (the ViewModel). I've debugged calling GetValue(TextProperty) and this returns the correct value so the DP dictionary is correctly updating.
Given the following Binding in the parent Window's XAML:
<src:ApplicationWindowBase x:Class="xxx.Windows.Client.Filing"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:src="clr-namespace:xxx.Windows.Core;assembly=MIGTurbo1.Windows.Core"
xmlns:viewmodel="clr-namespace:xxx.Windows.Client.ViewModel"
xmlns:converters="clr-namespace:xxx.Windows.Client.Converters"
xmlns:xxx_Windows_Core_Controls="clr-namespace:xxx.Windows.Core.Controls;assembly=xxx.Windows.Core"
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"
Title="File Item(s)" Height="450" Width="550" ShowInTaskbar="False">
<src:ApplicationWindowBase.Resources>
<viewmodel:ViewModelLocator x:Key="ViewModelLocator" d:IsDataSource="True"/>
<converters:FileableItemNameStringConverter x:Key="fileableItemNameStringConverter" />
<converters:FileableItemTypeStringConverter x:Key="fileableItemTypeStringConverter" />
<converters:FileableItemMetaDataStringConverter x:Key="fileableItemMetaDataStringConverter" />
<converters:FileableItemIconConverter x:Key="fileableItemIconConverter" />
</src:ApplicationWindowBase.Resources>
<src:ApplicationWindowBase.DataContext>
<Binding Mode="OneWay" Path="Filing" Source="{StaticResource ViewModelLocator}"/>
</src:ApplicationWindowBase.DataContext>
<Grid>
<!-- SNIP -->
<xxx_Windows_Core_Controls:TemplateEditor Grid.Column="1" Grid.Row="1" Margin="0" Text="{Binding Comment, Mode=TwoWay}" d:LayoutOverrides="Width, Height"/>
<!-- SNIP -->
</src:ApplicationWindowBase>
I am using MVVM Light and the ViewModel does bind correctly. There are other control/field bindings (ommitted) that work fine. THe problem is that the Text property binding on the Comment ViewModel property is not working. I can set it fine (ie. set value in ViewModel, then bind) but the user-entered value in Text never goes into the ViewModel Comments property.
What am I doing wrong?
Try this:
<TextBox Text="{Binding ElementName=userControl,Path=Text,Mode=TwoWay}" />
and remove handlers.
Ok, typical StackOverflow usage pattern adopted.
I realised I am writing this one-way, have added a TextChanged event handler to update the dependency property.
public partial class TemplateEditor : UserControl
{
public string Text
{
get
{
string s=(string)GetValue(TextProperty);
return s;
}
set
{
if (Text != value)
{
SetValue(TextProperty, value);
}
}
}
public static readonly DependencyProperty TextProperty =
DependencyProperty.Register("Text", typeof(string), typeof(TemplateEditor),new PropertyMetadata(null,Text_PropertyChanged));
public TemplateEditor()
{
InitializeComponent();
textBox.TextChanged += new TextChangedEventHandler(textBox_TextChanged);
}
private static void Text_PropertyChanged(DependencyObject source, DependencyPropertyChangedEventArgs e)
{
((TemplateEditor)source).textBox.Text = (string)e.NewValue;
}
void textBox_TextChanged(object sender, TextChangedEventArgs e)
{
Text = textBox.Text;
}
}
Thanks for your time, and apologies. :)
I am switching content template of a ListViewItem at run mode to enable editting the item. For that I am showing a Panel with Ok and Cancel options and I need the user to select any of those option before moving to anotheritem. I want that Panel to behave like a modal Dialog.
Any suggestions?
Advanced Thanks,
Das
You could try listen to PreviewLostKeyboardFocus event and mark it as handled when you don't want to let focus go. Here is an example. We have two columns, and if you put focus into the first column you never go out from it until you click Release Focus button:
XAML
<Window x:Class="WpfApplication1.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Focus Sample" Height="300" Width="340">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<GroupBox Header="Press Release Focus to leave">
<StackPanel PreviewLostKeyboardFocus="StackPanel_PreviewLostKeyboardFocus">
<TextBox/>
<Button Content="Release Focus"
Click="ReleaseFocusClicked"/>
</StackPanel>
</GroupBox>
<GroupBox Header="Try to switch focus here:"
Grid.Column="1">
<TextBox/>
</GroupBox>
</Grid>
</Window>
C#
using System.Windows;
using System.Windows.Input;
namespace WpfApplication1
{
public partial class Window1 : Window
{
private bool _letGo;
public Window1()
{
InitializeComponent();
}
private void StackPanel_PreviewLostKeyboardFocus(object sender, KeyboardFocusChangedEventArgs e)
{
var uie = (UIElement) sender;
var newFocusDO = (DependencyObject)e.NewFocus;
if (!_letGo && !uie.IsAncestorOf(newFocusDO))
{
e.Handled = true;
}
}
private void ReleaseFocusClicked(object sender, RoutedEventArgs e)
{
_letGo = true;
}
}
}
I'm doing one extra check to ensure whether new focus target belongs to our panel. If we don't do this we never let focus leave from the currently focused element. It worth to mention that this approach doesn't keep user from clicking on other buttons in UI. It just holds focus.
Hope this helps.
Cheers, Anvaka.