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));
}
}
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.
In XAML/WPF I have main window which contains Frame where I intend to put one of the user controls for given view of application.
<Window x:Class="MyApp.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:ignore="http://www.galasoft.ch/ignore"
mc:Ignorable="d ignore"
DataContext="{Binding Main, Source={StaticResource Locator}}">
<Grid x:Name="LayoutRoot">
<Frame Source="Main/MainUserControl.xaml" Name="Main" />
</Grid>
</Window>
Now I want to navigate this Frame to other source inside MainUserControl:
<UserControl x:Class="MyApp.View.MainMenu.MainMenuUserControl"
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:lex="http://wpflocalizeextension.codeplex.com"
xmlns:command="clr-namespace:MyApp.Command"
mc:Ignorable="d"
Style="{StaticResource Localizable}"
DataContext="{Binding MainMenu, Source={StaticResource Locator}}">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="1*"/>
</Grid.RowDefinitions>
<Button Content="{lex:Loc About}" FontSize="28" Grid.Row="1" Command="NavigationCommands.GoToPage" CommandParameter="/Menu/AboutUserControl.xaml" />
</Grid>
</UserControl>
But the navigation button About remains inactive during execution. I verified correctly that /Menu/AboutUserControl.xaml exists.
I'm obviously doing something wrong. How can I navigate owning window's frame from within user control? Preferably via XAML?
I assume you are using an MVVM framework. (I have added the critical elements here in case you aren't).
Your MainWindow.xaml should use an "ItemsControl" instead of a "Frame". A frame can work, but a better way is to use the ItemsControl like so:
<!-- Main Frame -->
<Grid Grid.Column="1" Margin="10" Name="MainWindowFrameContent">
<ItemsControl ItemsSource="{Binding Path=MainWindowFrameContent}" >
<!-- This controls the height automatically of the user control -->
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<UniformGrid Columns="1" IsItemsHost="True"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ItemsControl>
</Grid>
In the constructor of my MainWindow.cs, I set the DataContext of the window to the MainViewModel:
using myProject.ViewModel;
public partial class MainWindow : Window
{
MainViewModel mMainViewModel;
public MainWindow()
{
InitializeComponent();
// Initialize MainViewModel and set data context to the VM
mMainViewModel = new MainViewModel();
DataContext = mMainViewModel;
}
}
(I'm not sure if this next part HAS TO be an observable collection, but I have implemented it as such and it seems to work well. The only downside is that I need to manually clear the ItemsControl before adding a new UserControl)
My MainViewModel implements the binding called "MainWindowFrameContent". All of my user controls are initialized within the MainViewModel.cs code. I have an additional ViewModel for each UserControl and assign the DataContext of the UserControl to the individual ViewModel before displaying the UserControl to the main window.
My MainViewModel.cs:
public class MainViewModel : ObservableObject
{
public MainViewModel()
{
}
// This handles adding framework (UI) elements to the main window frame
ObservableCollection<FrameworkElement> _MainWindowFrameContent = new ObservableCollection<FrameworkElement>();
public ObservableCollection<FrameworkElement> MainWindowFrameContent
{
get
{
return _MainWindowFrameContent;
}
set
{
_MainWindowFrameContent = value;
RaisePropertyChangedEvent("MainWindowFrameContent");
}
}
// This handles opening a generic user control on the main window
// The ICommand implementation allows me to bind the command of a button on the main window to displaying a specific page
public ICommand MainWindowDataEntryView
{
get
{
return new DelegateCommand(_MainWindowDataEntryView);
}
}
void _MainWindowDataEntryView(object obj)
{
DataEntryVM wDataEntryVM = new DataEntryVM();
DataEntryView wDataEntryView = new DataEntryView();
wDataEntryView.DataContext = wDataEntryVM;
MainWindowFrameContent.Clear();
MainWindowFrameContent.Add(wDataEntryView);
}
}
Then you need to make sure you have an ObservableObject.cs as part of your project:
using System.ComponentModel;
public class ObservableObject : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected void RaisePropertyChangedEvent(string propertyName)
{
var handler = PropertyChanged;
if (handler != null)
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
And you need a DelegateCommand.cs class as part of your project:
using System;
using System.Windows.Input;
public class DelegateCommand : ICommand
{
private readonly Action<object> _action;
public DelegateCommand(Action<object> action)
{
_action = action;
}
public void Execute(object parameter)
{
_action(parameter);
}
public bool CanExecute(object parameter)
{
return true;
}
#pragma warning disable 67
public event EventHandler CanExecuteChanged { add { } remove { } }
#pragma warning restore 67
}
So, it's a bit of a lengthy explanation, but once you have the previous items set up, you can add a bunch of buttons to your MainWindow.xaml, bind each button to a command that adds a new UserControl to your ItemsControl. When your UserControl displays, you can add controls as you would like and use them.
I hope this helps!
I have a MainWindow containing a UserControl, both implemented in MVVM-pattern.
The MainWindowVM has properties that I want to bind to properties in the UserControl1VM. But this doesn't work.
Here's some code (the viewmodels use some kind of mvvm-framework that implement the INotifyPropertyChanged in a ViewModelBase-class but that's hopefully no problem):
MainWindow.xaml:
<Window x:Class="DPandMVVM.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:DPandMVVM"
Title="MainWindow" Height="300" Width="300">
<Grid>
<local:UserControl1 TextInControl="{Binding Text}" />
</Grid>
</Window>
CodeBehind MainWindow.xaml.cs:
using System.Windows;
namespace DPandMVVM
{
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
DataContext = new MainWindowVM();
}
}
}
MainWindow-ViewModel MainWindowVM.cs:
namespace DPandMVVM
{
public class MainWindowVM : ViewModelBase
{
private string _text;
public string Text { get { return _text; } }
public MainWindowVM()
{
_text = "Text from MainWindowVM";
}
}
}
And here the UserControl1.xaml:
<UserControl x:Class="DPandMVVM.UserControl1"
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>
<TextBlock Text="{Binding TextInTextBlock}" />
</Grid>
</UserControl>
The Codebehind UserControl1.xaml.cs:
using System.Windows.Controls;
namespace DPandMVVM
{
/// <summary>
/// Interaction logic for UserControl1.xaml
/// </summary>
public partial class UserControl1 : UserControl
{
public UserControl1()
{
InitializeComponent();
DataContext = new UserControl1VM();
}
}
}
And the Viewmodel UserControl1VM.cs:
using System.Windows;
namespace DPandMVVM
{
public class UserControl1VM : DependencyObject
{
public UserControl1VM()
{
TextInControl = "TextfromUserControl1VM";
}
public string TextInControl
{
get { return (string)GetValue(TextInControlProperty); }
set { SetValue(TextInControlProperty, value); }
}
public static readonly DependencyProperty TextInControlProperty =
DependencyProperty.Register("TextInControl", typeof(string), typeof(UserControl1VM));
}
}
With this constellation the DP cannot be found in MainWindow.xaml.
What am I doing wrong?
First of all you want DependencyProperty TextInControl to be declared inside UserControl1 if you want to bind it from outside.
Move the declaration of DP inside of UserControl1.
public partial class UserControl1 : UserControl
{
public UserControl1()
{
InitializeComponent();
}
public string TextInControl
{
get { return (string)GetValue(TextInControlProperty); }
set { SetValue(TextInControlProperty, value); }
}
public static readonly DependencyProperty TextInControlProperty =
DependencyProperty.Register("TextInControl", typeof(string),
typeof(UserControl1));
}
Second you have externally set DataContext of UserControl to UserControl1VM,
public UserControl1()
{
InitializeComponent();
DataContext = new UserControl1VM(); <-- HERE (Remove this)
}
So WPF binding engine looking for property Text in UserControl1VM instead of MainWindowVM. Remove setting DataContext and update XAML of UserControl1 to this:
<UserControl x:Class="DPandMVVM.UserControl1"
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"
x:Name="userControl1">
<Grid>
<TextBlock Text="{Binding TextInTextBlock, ElementName=userControl1}" />
</Grid>
</UserControl>
Bind DP using ElementName by setting x:Name on UserControl.
UPDATE
In case you want to have ViewModel intact for UserControl, you have to update binding in MainWindow. Explicitly tell WPF binding engine to look for property in MainWindow's DataContext using ElementName in binding like this:
<local:UserControl1 TextInControl="{Binding DataContext.Text,
ElementName=mainWindow}" />
For this you need to set x:Name="mainWindow" on window root level.
The XAML of your control right now reference the property TextInTextBlock via the DataContext which in turn "Points" to your main window's view model. Reference the data of the control and you are done (btw do not set the DataContext for that reason - the binding won't work any more):
<UserControl x:Class="DPandMVVM.UserControl1"
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"
x:Name="self">
<Grid>
<TextBlock Text="{Binding TextInTextBlock, ElementName=self}" />
</Grid>
</UserControl>
This is how I do UserControls with MVVM and DP binding. It's similar to Rohit's answer but with some slight changes. Basically you need to set the Control's internal view model to be the DataContext of the root container within the UserControl rather than the UserControl itself, that way it will not interfere with DP bindings.
E.g.
UserControl XAML
<UserControl x:Class="DPandMVVM.UserControl1"
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"
x:Name="userControl1">
<Grid x:Name="Root">
<TextBlock Text="{Binding TextFromVM}" />
</Grid>
UserControl Code-behind
public partial class UserControl1 : UserControl
{
public UserControl1()
{
InitializeComponent();
this.ViewModel = new UserControlVM();
}
public UserControlVM ViewModel
{
get { return this.Root.DataContext as UserControlVM ; }
set { this.Root.DataContext = value; }
}
public string TextFromBinding
{
get { return (string)GetValue(TextFromBindingProperty); }
set { SetValue(TextFromBindingProperty, value); }
}
public static readonly DependencyProperty TextFromBindingProperty =
DependencyProperty.Register("TextFromBinding", typeof(string), typeof(UserControl1), new FrameworkPropertyMetadata(null, OnTextBindingChanged));
private static void OnTextBindingChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var uc = d as UserControl1;
uc.ViewModel.TextFromVM = e.NewValue as string;
}
}
This means that the control derives it's values from the Root element DataContext which is our ViewModel but the ViewModel can be updated via a DP binding from outside the control (in your case a binding to the parent Window's ViewModel, see below)
Window XAML
<Window x:Class="DPandMVVM.Window1"
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:DPandMVVM"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300"
x:Name="window1">
<Grid x:Name="Root">
<local:userControl1 TextFromBinding="{Binding TextFromWindowVM}" />
</Grid>
I have a method that I believe is a lot simpler, and probably more true to MVVM.
In the main window XAML:
<myNameSpace:myUserControl DataContext="{Binding Status}"/>
In your main view model (the data context of the main window:
public myUserControlViewModel Status { set; get; }
now you can in the constructor (or whenever you want to instantiate it):
Status = new myUserControlViewModel();
then if you want to set the text property:
Status.Text = "foo";
and make sure you have the binding setup to a property named Text inside your myUserControlViewModel class:
<TextBox Text="{Binding Text}"/>
and make sure the property fires PropertyChanged, of-course.
Plus, if you use Resharper. You can create a Design instance of the UserControl in your XAML so that it can link the bindings and not tell you that the property is never used by doing this:
<UserControl x:Class="myNameSpace.myUserControl"
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:myNameSpace="clr-namespace:myNameSpace"
d:DataContext="{d:DesignInstance myNameSpace.myUserControl}"
mc:Ignorable="d" ...>
This part:
xmlns:myNameSpace="clr-namespace:myNameSpace"
d:DataContext="{d:DesignInstance myNameSpace.myUserControl}"
Here's a possible working solution for you. However, I've noted in a comment above that this will work in code and perhaps (like my situation) will show up as an error (Object Not Found) in the designer:
<local:UserControl1 TextInControl="{Binding DataContext.Text,
Source={x:Reference <<Your control that contains the DataContext here>>}}" />
I'd rather to have a cleaner solution, though, without any designer errors. I wish to find out how to properly bind a dependency property in a user control to a value coming from the window it's contained in. What I'm finding is that whatever I try to do (short of what I showed above), such as using ElementName and/or AncestorType/Level, etc., the debugger complains that it can't find the source and shows that it's looking for the source inside the context of the user control! It's like I can't break out of the user control context when doing Binding logic in the use of that control (other than that "designer-breaking" solution above).
UPDATE:
I noticed that this might not work for you as your situation might force a problem I just noticed if I change my own source to reference the window instead of a control that has the data context. If I reference the window then I end up with a cyclical redundancy. Perhaps you'll figure out a way to use the Source version of the binding that will work okay for you.
I must also add that my situation is probably a bit more complex since my usercontrol is used in the context of a popup.
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. :)
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?