Get reference to nested control's sub control? - wpf

MainWindow.xaml has view model MainWindowViewModel.
MainWindow.xaml has a nested User Control called
CustomBrowserControl.xaml
CustomBrowserControl.xaml has a named element webBrowser.
MainWindowViewModel has a command that needs a reference to
webBrowser.
How do I pass the reference?
The Solution I Came Up With
Based on EthicalLogics and sa_ddam213's responses, yes, in the code behind of my MainWindow, if I named the user control (in the xaml, add the attribute x:Name="something"), I could then reference the user control object. I could then pass that reference to the MainWindowViewModel. This is also apparently bad practice, because it breaks MVVM.
So what I did instead
In my user control, I created two new depdency properties as follows:
public static readonly DependencyProperty TakePictureCommand = DependencyProperty.Register("TakePicture", typeof(ICommand), typeof(BrowserControl));
public ICommand TakePicture
{
get { return (ICommand)GetValue(TakePictureCommand); }
set { SetValue(TakePictureCommand, value); }
}
Now in my MainWindow.xaml, I placed a button. I was able to bind the button to the TakePicture command by using the following xaml:
<Window>
<Button Content="Take Picture" Command="{Binding ElementName=browserControl, Path=DataContext.TakePicture}" FocusManager.IsFocusScope="True" ...>
<myUserControls:BrowserControl x:Name="browserControl" ... />
</Window>
This way I didn't need to pass a reference at all, and could just let the command / method in the user control, be invoked by the action on the main window.
Thank you so much to those who responded!!

I don't think its a good practice to have references of Controls in ViewModel in MVVM . But you can create a property of Type WebBrowser Element in ViewModel and assign it like
((MainWindowViewModel)this.DataContext).WebBrowserProperty=CustomBrowserControl.webBrowser
I hope this will help.

Related

MVVM + UserControl + Dependency Property

Alright, this is somewhat related to this question: WPF Printing multiple pages from a single View Model
I tried to follow the advice given there but now I am stuck.
My application uses a MainView.xaml and the appropriate MainViewViewModel.cs, I am using MVVM Light in the background.
Now - according to the post - it seems I have to do the following:
Create a user control
Expose some properties from the user control
Make sure the view model shows these properties
The idea is clear but I am stuck when trying to notify each other.
My user control (UcTest.xaml) exposes a Dependency Property:
public string SpecialText
{
get { return (string)GetValue(SpecialTextProperty); }
set
{
SetValue(SpecialTextProperty, value);
}
}
// Using a DependencyProperty as the backing store for SpecialText. This enables animation, styling, binding, etc...
public static readonly DependencyProperty SpecialTextProperty =
DependencyProperty.Register("SpecialText", typeof(string), typeof(UcTest), new PropertyMetadata(new PropertyChangedCallback(SpecialTextChangedPropertyCallback)));
private static void SpecialTextChangedPropertyCallback(DependencyObject sender, DependencyPropertyChangedEventArgs e)
{
// Do something
Debug.WriteLine("Ffgdgf");
}
Alright, so I do now have a user control which has some dependency properties. Yet, these properties are completely separated from my ViewModel properties (those are the ones which shall be displayed).
So basically I have two possibilities:
How can I now tell my ViewModel for the UserControl that some properties have changed?
Is there a possibility to forget about the dependency properties and access the view model directly?
Additional info #1:
I have uploaded a (simple) example of what I am trying to do here: Example Project. I would like to change the value of the label in UserControl1 (via the binding property in the ViewModel for UserControl1) from my MainViewViewModel.
You would usually bind the UserControl's property to the ViewModel property. A two-way binding would work in both directions, from ViewModel to View and vice versa.
<Window x:Class="TestApplication.MainWindow" ...>
<Window.DataContext>
<local:MyViewModel/>
</Window.DataContext>
<Grid>
<local:UcTest SpecialText="{Binding MyViewModelProperty, Mode=TwoWay}"/>
</Grid>
</Window>
To directly access the ViewModel object in the above example, you could simply cast the UserControl's DataContext property to the ViewModel type. The DataContext is inherited from the MainWindow.
var viewModel = DataContext as MyViewModel;
var property = viewModel.MyViewModelProperty;
You could of course also directly assign a specialized ViewModel instance to the UserControl's DataContext:
<local:UcTest SpecialText="{Binding MyViewModelProperty, Mode=TwoWay}"/>
<local:UcTest.DataContext>
<local:UserControlViewModel/>
</local:UcTest.DataContext>
</local:UcTest>
or you may create the ViewModel instance as a resource in a resource dictionary and assign the DataContext like this
<local:UcTest DataContext="{StaticResource MyUserControlViewModel}"
SpecialText="{Binding MyViewModelProperty, Mode=TwoWay}"/>
Alright, after hours of googling it appears that the "correct" approach to this is not to do it at all. The general approach is to keep the data in your MainViewModel and not use an additional ViewModel for the UserControl (which I find a little ... well .. not so good). The main problem is that there is no easy mechanism to get the Data from the Dependency Property to the ViewModel.
For printing, I have now gone back to doing it purely in code.

WPF Binding to UserControl Custom DependencyProperty

I have a custom UserControl called SongDescription:
<UserControl x:Class="DPTestAp.SongDescription" ...>
<Grid x:Name="LayoutRoot">
<DockPanel Height="50">
<TextBlock x:Name="title" Text="{Binding name}" Width="100" Height="30"/>
<TextBox x:Name="lyrics"/>
</DockPanel>
</Grid>
</UserControl>
I added DependencyProperty to it:
public partial class SongDescription : UserControl
{
public static readonly DependencyProperty SongProperty = DependencyProperty.Register("Song", typeof(Song), typeof(SongDescription));
public Song Song
{
get
{
return (Song)GetValue(SongProperty);
}
set
{
SetValue(SongProperty, value);
updateLyrics()
}
}
private void updateLyrics()
{
lyrics.Text = Song.lyrics;
}
public SongDescription()
{
InitializeComponent();
}
}
The question is: how to bind something to this SongProperty?
I use SongDescription in my main window like this:
<local:SongDescription x:Name="songDescription" Song="{Binding DataContext}"/>
I cannot make my TextBox lyrics show lyrics. In main window I tried to set DataContext to songDescription, like this:
songDescription.DataContext = new Song() { name="Home", lyrics="Hold on, to me as we go" };
or to window itself like this:
DataContext = new Song() { name="Home", lyrics="Hold on, to me as we go" };
I even tried to make Song a resource and bind it to SongProperty like this:
<Window.Resources>
<local:Song x:Key="res" name="Home" lyrics="Hold on, to me as we go"/>
</Window.Resources>
<Grid>
<local:SongDescription x:Name="songDescription" Song="{StaticResource res}"/>
</Grid>
Nothing helped. TextBlock title binds song name fine. But I can't make updateLyrics() method be called. (In real life this method is more complicated, so I can't use Binding like with name).
Thank you!
Yup, so that's a gotcha with dependency properties. You never ever put validation code inside of the accessor methods (get/set) because dependency properties are stored by WPF in a table that it itself manages. This is why you have to register dependency properties, it essentially creates entries on this table for storing the values associated with each property, and when you use 'GetValue' / 'SetValue' you are updating the entries on this table (which by the way relates to how WPF is able to manage data bindings in general).
The upshot of this though is that WPF can (and will) completely bypass your property accessors because it has direct access to the real data. Why should it use your accessors if it can just go to the data directly. Instead you need to implement a 'PropertyChanged' callback function or some WPF sanctioned method of doing validation, but never ever do it in your accessors.
See:
http://msdn.microsoft.com/en-us/library/ms752914.aspx
In addition to sircodesalot's answer, you are not bound on your lyrics textbox. Also, since the song your bound to is a class, you will need to specify the paths fully for the properties you want to show in the boxes such as "Path=Song.Lyrics".
Another thing to consider is that with dependency properties; your mode will be oneway by default so making the text field editable would be moot really unless you change it.
Third, if you're using MVVM you only need your main window context to be set to the view model and have a matching Song property to bind against.

Can I add a DependencyProperty on an windows user control?

I'm trying to host a Visio ActiveX object in a WPF application.
To do this, I created a Windows user control project where I add the Visio object. This windows user control is then hosted on an WPF user control in an WindowsFormsHost object.
<WindowsFormsHost Name="wfHost" Grid.Row="1">
<wf:VisioUserControl FileNamePath="?"/>
</WindowsFormsHost>
What I would like to do is to bind the value of the FileNamePath member to the value of a TextBox element which defines the path.
The project follows the MVVM pattern, so there is no way that I can access the VisioUserControl object in my ViewModel.
The solution I was thinking about is to bind the FileNamePath member to the value of the TextBox that contains the path, but it is not a DependencyProperty and it seems that I'm not able to define one in the code behind of the windows user control.
So, is there any workaround to perform this binding?
Thanks in advance.
You can solve this by creating a UserControl that wraps your VisioUserControl (I wrote a simple tutorial on UserControl creation here). You can then add a FileNamePath dependency property to your UserControl. In the property changed handler of this dependency property, set the FileNamePath property on the VisioUserControl that this user control wraps.
Ok I have created an example of a WPF usercontrol that is hosting a Winforms control, with a dependency property that is bound to the winforms control's text property.
public partial class ActiveXObjectHoster : UserControl
{
private static System.Windows.Forms.Label testObject;
public ActiveXObjectHoster()
{
InitializeComponent();
testObject = new System.Windows.Forms.Label();
windowsFormsHost1.Child = testObject;
}
#region Properties
public static DependencyProperty FileNameProperty = DependencyProperty.Register("FileName", typeof(string), typeof(ActiveXObjectHoster), new UIPropertyMetadata("",new PropertyChangedCallback(OnFileNamePropertyChanged)));
public string FileName
{
get { return (string)GetValue(FileNameProperty); }
set
{
SetValue(FileNameProperty, value);
}
}
private static void OnFileNamePropertyChanged(
DependencyObject sender, DependencyPropertyChangedEventArgs e)
{
testObject.Text = (string)e.NewValue;
}
#endregion
}
Here is the xaml of the control (its very simple)
<UserControl xmlns:my="clr-namespace:System.Windows.Forms.Integration;assembly=WindowsFormsIntegration"
x:Class="WPFTestApp2.Controls.ActiveXObjectHoster"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
x:Name="ObjectHost"
Height="100" Width="100">
<Grid>
<my:WindowsFormsHost x:Name="windowsFormsHost1" />
</Grid>
</UserControl>
What you need to do is change the test object from a Label to whatever Visio object you were using. Then in the property callback change the text property to the filename or whatever property you wanted.
As mentioned above this is done in the code behind, but that is fine for a user control, its completely decoupled from whatever thing is using it, you just need to bind to the filename property of the control.
Here is a link to a project I created showing how the control is used. There is a textbox whos text is bound to the FileName property, which changes the Winforms Labels text.
You can place this in a Winforms Usercontrol if you want to use it in winforms (like you mentioned in your reply to my comment)
Try replacing the label for your control and see if it works.
Why not implement a UserControl to wrap the WindowsFormHost and the Visio user control? Then you cann add a Dependency Property, and implement in the code behind a handler for the PropertyChangedCallback, and appropiately interact with the WinForms control

Recommend best approach in this situation

I'm experimenting with MVVM and I can't quite wrap my mind around it.
I have a View (windows) that has several repeated controls.
Let's say I have 4 textbox - button pairs. Their behavior should be the same, after pressing a button paired textbox says "Hello World!"
I have tried several options I could think of:
One ViewModel, 4 string properties binded to textboxes, 1 command
When I bind each button to the same command I can't tell which property needs to be set.
Passing enum to CommandParameter feels awkward.
One ViewModel and UserControl that hosts a textbox and a button repeated 4 times.
Now I need to expose all the properties like Command, CommandParameter, Text etc.. Seems like a lot of work.
Still can't say which property needs to be updated after click.
Each UserControl has a ViewModel
This solves button clicking and setting property, but now I have no clue how to extract texts from nested ViewModels to the window ViewModel.
Is there any other way? I suspect DataTemplates could be of use, but I'm not sure how.
What you describe is such an abstract and contrived idea that it doesn't warrant MVVM. You're talking about TextBoxes and Buttons, which are all 'View', and not the MVVM way of thinking. You'd almost always start with a Model.
There is no 'Model' per-se here though; your specification is literally to set the value of a TextBox on a Button click. The seemingly random list of '4' items (picked out of nowhere) and a seemingly useless TextBox mean nothing.
Putting that aside and assuming that you have a set of 4 business entities, each with a field on them that is user-editable, and an action that the user can trigger, you'd do this:
Create a ViewModel class to represent an item - eg MyItemModel
Create a ViewModel class to represent the set of items (likely it will just return a Collection of the first) - eg AllMyItemsListModel
Then for the View:
Create an ItemsControl, with ItemsSource bound to an instance of the 'collection' of the second ViewModel class
For each ItemTemplate, have a template or UserControl for each item
Within the template or UserControl, bind the TextBox's Text property to the appropriate property of the first class
Bind the Button's Command property to a property on the first class returning an ICommand - using RelayCommand for example
I don't know what you mean about 'extracting texts from nested ViewModels to the window ViewModel' - what does that mean and why would you want to do it?
Hope that helps.
Number 3. Except there would just be one UserControl with viewmodel and then the Main page that would have multiple instances of that UserControl on it. If the main window needs info from the UserControl you could pass it through events or use something like MVVM Light and it's messenger class.
There's no need to create a separate ViewModel for a reusable control that has such simple behavior. Just by adding a few DependencyProperties and an event handler to the simple UserControl you can reuse the logic and only set the properties that are actually different on each instance. For the UserControl XAML you just need to hook up the TextBox to the DependencyProperty and the Button to a Click handler.
<DockPanel>
<Button Content="Reset" Click="Button_Click"/>
<TextBox Text="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type UserControl}}, Path=Text}"/>
</DockPanel>
The code for the UserControl just needs to define the properties that can be bound externally and the handler to reset the Text.
public partial class ResetTextBox : UserControl
{
public static readonly DependencyProperty TextProperty = DependencyProperty.Register(
"Text",
typeof(string),
typeof(ResetTextBox),
new FrameworkPropertyMetadata(null, FrameworkPropertyMetadataOptions.BindsTwoWayByDefault));
public string Text
{
get { return (string)GetValue(TextProperty); }
set { SetValue(TextProperty, value); }
}
public static readonly DependencyProperty ResetTextProperty = DependencyProperty.Register(
"ResetText",
typeof(string),
typeof(ResetTextBox),
new UIPropertyMetadata(String.Empty));
public string ResetText
{
get { return (string)GetValue(ResetTextProperty); }
set { SetValue(ResetTextProperty, value); }
}
public ResetTextBox()
{
InitializeComponent();
}
private void Button_Click(object sender, RoutedEventArgs e)
{
Text = ResetText;
}
}
Then to use the control you just need to bind to your ViewModel string properties and set the default text that should be applied on a reset which can either be hardcoded here, or bound to other VM properties.
<StackPanel>
<local:ResetTextBox ResetText="One" Text="{Binding Name1}"/>
<local:ResetTextBox ResetText="Two" Text="{Binding Name2}"/>
<local:ResetTextBox ResetText="Three" Text="{Binding Name3}"/>
</StackPanel>

WPF User Control hell with MVVM and Dependency Properties

This is what I'm trying to do:
I'm writing a UserControl that I want to be consumed by other developers.
I want end users to be able to use my control using Dependency Properties.
<lib:ControlView ControlsText={Binding Path=UsersOwnViewModelText} />
I'm using the MVVM pattern.
I'm binding my ViewModels to their View's using <DataTemplates>
<DataTemplate DataType="{x:Type local:ControlViewModel}">
<local:ControlView />
</DataTemplate>
So I have two questions:
Am I right in thinking that if a UserControl is being consumed in XAML then the UserControl must set the ViewModel as its DataContext when the control's Loaded event fires instead of using the <DataTemplate> method?
How do I allow users to data bind to my control's dependency properties while still being data bound to my ViewModel?
You should separate the two use cases:
The (user) control that will be consumed by other developers.
The user control that will be consumed by your application.
Importantly, the latter depends on the former - not vice versa.
Use case 1 would use dependency properties, template bindings, all the things that go into making a regular WPF control:
MyControl.cs:
public class MyControl : Control
{
// dependency properties and other logic
}
Generic.xaml:
<ControlTemplate Type="local:MyControl">
<!-- define the default look in here, using template bindings to bind to your d-props -->
</ControlTemplate>
You would then define use case 2 as:
MyViewModel.cs:
public class MyViewModel : ViewModel
{
// properties and business logic
}
MyView.xaml:
<UserControl ...>
<local:MyControl SomeProperty="{Binding SomePropertyOnViewModel}" .../>
</UserControl>
Best of both worlds with a clean separation. Other developers depend only on the control, which could (and probably should) be in a completely different assembly than your view model and view.
First off, I don't think MVVM is a good choice if you are developing a UserControl that will be consumed by others. A lookless control is what you really should be developing. Jeremiah Morrill has a blog post about this subject.
With that said, you can set the datacontext with XAML if you have a default public constructor.
Inside ControlView.xaml put:
<UserControl.DataContext>
<local:ControlViewModel />
</UserControl.DataContext>
Basically, instead of binding your UserControl's datacontext to the userControlViewModel, it's better to do it on the first child element of the user control. That way, all the references that you make within the control will be bound to the userControlViewModel, but the dependencies properties can be set from the data context set where you want to use your UserControl.
This is from a project I'm working at:
<UserControl x:Class="Six_Barca_Main_Interface.MyUserControl"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:Six_Barca_Main_Interface"
xmlns:System="clr-namespace:System;assembly=mscorlib"
mc:Ignorable="d"
d:DesignHeight="900" d:DesignWidth="900">
<DockPanel x:Name="rootDock" >
<TextBlock>{Binding SomethingInMyUserControlViewModel}</TabControl>
</DockPanel>
</UserControl>
Then on the code behind:
public partial class MyUserControl : UserControl
{
UserControlViewModel _vm;
public MyUserControl()
{
InitializeComponent();
//internal viewModel set to the first child of MyUserControl
rootDock.DataContext = new UserControlViewModel();
_vm = (UserControlViewModel)rootDock.DataContext;
//sets control to be able to use the viewmodel elements
}
#region Dependency properties
public string textSetFromApplication
{
get{return (string)GetValue(textSetFromApplicationProperty);}
set{SetValue(textSetFromApplicationProperty, value);}
}
public static readonly DependencyProperty textSetFromApplicationProperty = DependencyProperty.Register("textSetFromApplication", typeof(string), typeof(MyUserControl), new PropertyMetadata(null, OnDependencyPropertyChanged));
private static void OnDependencyPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
((MyUserControl)d)._vm.SomethingInMyUserControlViewModel =
e.NewValue as string;
}
#endregion
Then when you use this on your main view, you can set the dependency property with the value you want to pass to MyUSerControl
A UserControl is part of the "View" in "MVVM" just like the TextBox or ListView controls are part of the View.
Whether you decide to use MVVM to develop your UserControl itself or write it in QBASIC (not recommended) it does not break the MVVM pattern for the consumers of your UserControl so long as they can do every thing they need with your UserControl by binding to DependencyProperty's exposed on your UserControl. i.e. Your UserControl should expose the properties it is dependent upon (hence the name). Once you grasp this DependencyProperty's suddenly make a whole lot of sense and you want their helpful on changed event handlers and default values you specify in their constructor.
If your UserControl is in a different assembly or not I cannot see how that makes a difference.
That said many would advocate you build your UserControl using the MVVM pattern itself for all the good reasons MVVM brings e.g. helping another developer looking at your code. However some things simply are not possible and/or much harder more complex and less performant hacking the XAML to do this - I am not talking about your garden variety Add User Form but for example a UserControl handling the layout of thousands of visuals. Furthermore since you are working in your View you do NOT want your UserControl's ViewModels mixed in with you applications!
Basically I am saying it is well within MVVM not to use MVVM on your View!

Resources