Let's say I have a user control test1.xaml and has a frame control name frame1. On my second user control, how do I reference the test1.xaml in my test2.xaml in order to manipulate the property of the control in test2.xaml.cs? Because I know that test1 test = new test1(); will not work because im not instantiating it and not referencing it. May I ask how?
In the MVVM approach both views / user controls could use the same view model that they are data bound to. Now when the first control causes a value change in one of the properties exposed by that viewmodel this is automatically reflected in the second user control.
Ok. I will not write the code with DependencyProperties because It smells. I will write instead a simple code which does such stuff using MVVM. But I want to note that you must read an article "WPF Apps With The Model-View-ViewModel Design Pattern" by Josh Smith.
Here is a simple code which contains one main window and two user controls Test1 and Test2. And only one ViewModel - GodViewModel, which is the viewModel for both Test1 and Test2. In fact, as a rule there are 1-1 mapping between ViewModel and View. I created only one ViewModel for the simplicity.
The window code:
<Window x:Class="WpfApplication99.MainWindow"
x:Name="GodWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:view="clr-namespace:WpfApplication99.View"
Title="MainWindow"
xmlns:local="clr-namespace:WpfApplication99"
DataContext="{Binding Vm, ElementName=GodWindow}">
<StackPanel>
<view:Test1 />
<view:Test2 />
</StackPanel>
</Window>
public partial class MainWindow : Window
{
ViewModel.GodViewModel _vm = new ViewModel.GodViewModel();
public ViewModel.GodViewModel Vm
{
get { return _vm; }
set { _vm = value; }
}
public MainWindow()
{
InitializeComponent();
}
}
The ViewModel code:
namespace WpfApplication99.ViewModel
{
public class GodViewModel
{
public string Text { get; set; }
}
}
test1 code (code behind is empty):
<UserControl x:Class="WpfApplication99.View.Test1"
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="45" d:DesignWidth="167">
<Button Content="{Binding Text}"
Height="26"
Name="button1"
Width="144" />
</UserControl>
test2 code (code behind is empty):
<UserControl x:Class="WpfApplication99.View.Test2"
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" >
<TextBox Text="{Binding Text}" Height="69" Width="232" />
</UserControl>
Using this code you can use the same property Text in both Test1 and Test2. In your question, you wrote that you had a property in test1 and want to use it in test2. So imagine that the solution provided just put the test1's one property Text into GodViewModel.
Perhaps, you want to use it in the code behind. In this case, you should create a separate ViewModel for test1 and test2 user controls. I'm not able to describe all details there. So, please read the article. I'm sure that MVVM pattern is a key thing in WPF.
You can add DependencyProperty of type YourViewModelBase to your test2. Than write some in the place you create an instance of your controls. Of course, if you use MVVM. However, as far as I understand, you should not do things like that according to MVVM.
if you don't have YourViewModelBase, you may create an abstract class for your test1 with necessary properties or just pass test1 as UserControl and then try to cast it to test1 in your test2 code.
Related
I'm building a WPF app with custom UserControls, and I'm trying to understand how property bindings are supposed to work. I can't get even the most basic binding to work, and it's simple enough to distill into a tiny example, so I figured someone with more WPF experience might be able to put me on the right track.
I've defined a custom UserControl called TestControl, which exposes a Foo property, which is intended to be set in XAML whenever a UserControl is placed.
TestControl.xaml.cs
using System.Windows;
using System.Windows.Controls;
namespace BindingTest
{
public partial class TestControl : UserControl
{
public static readonly DependencyProperty FooProperty = DependencyProperty.Register("Foo", typeof(string), typeof(TestControl));
public string Foo
{
get { return (string)GetValue(FooProperty); }
set { SetValue(FooProperty, value); }
}
public TestControl()
{
InitializeComponent();
}
}
}
The markup for TestControl just defines it as a control with a single button, whose label text displays the current value of the Foo property:
TestControl.xaml
<UserControl x:Class="BindingTest.TestControl"
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:BindingTest"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800">
<Grid>
<Button Content="{Binding Foo}" />
</Grid>
</UserControl>
In my MainWindow class, I just place a single instance of TestControl with its Foo property set to "Hello".
MainWindow.xaml
<Window x:Class="BindingTest.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:BindingTest"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<Grid>
<local:TestControl Foo="Hello" />
</Grid>
</Window>
I would expect that when I build and launch this app, I'd see a window with a single button reading "Hello". However, the button is blank: the Binding doesn't seem to work.
If I add a click handler to the TestControl's button, I can verify that the value is being updated behind the scenes:
// Added to TestControl.xaml.cs:
private void Button_Click(object sender, RoutedEventArgs e)
{
Console.WriteLine("Button clicked; Foo is '{0}'", Foo);
}
// Updated in TestControl.xaml:
// <Button Content="{Binding Foo}" Click="Button_Click" />
When I click the button, I get Button clicked; Foo is 'Hello', but the GUI never updates. I've tried using Path=Foo, XPath=Foo, etc., as well as setting UpdateSourceTrigger=PropertyChanged and verifying updates with NotifyOnTargetUpdated=True... nothing seems to result in the text in the UI being updated to match the underlying property value, even though the property value seems to be getting updated just fine.
What am I doing wrong? I feel like there's just a simple and fundamental misunderstanding in how I'm approaching this.
edit:
Poking around a bit more and reading similar questions has led me to a potential fix: namely, adding a name to the root UserControl element in TestControl.xaml (x:Name="control"), and changing the binding to explicitly specify that control ({Binding Foo, ElementName=control}).
I'm guessing that by default, {Binding Foo} on the Button element just means "find a property named 'Foo' on this Button control", whereas I'd assumed it'd mean "find a property named 'Foo' in the context that this Button is being declared in, i.e. on the TestControl".
Is specifying an explicit ElementName the best fix here?
You have to set the source object of the Binding to the UserControl instance, e.g. like this:
<Button Content="{Binding Foo, RelativeSource={RelativeSource AncestorType=UserControl}}"/>
or
<UserControl ... x:Name="theControl">
...
<Button Content="{Binding Foo, ElementName=theControl}"/>
If you have many such Bindings, you may also set the DataContext of the top level element in the UserControl's XAML to the UserControl instance:
<Grid DataContext="{Binding RelativeSource={RelativeSource AncestorType=UserControl}}">
<Button Content="{Binding Foo}" />
<Button Content="{Binding Bar}" />
</Grid>
You must however avoid to set the DataContext of the UserControl (which is often recommend by "expert" bloggers), because that would break DataContext-based Bindings of the UserControl properties like
<local:TestControl Foo="{Binding SomeFoo}" />
I have a problem implementing MVVM with a usercontrols.
I have an MVVM based application.
In one of the view (which is a usercontrol) I have a menu on the left and content on the right. The content change depending on the menu.
I tried to implement the MVVM with a usercontrol, but i dont know how.
Here is what i tried but it didn't work :
<UserControl x:Class="PoS.Views.OptionsView"
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:PoS.Views"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800">
<UserControl.Resources>
<DataTemplate x:Name="SettingsTemplate" DataType="{x:Type viewmodels:SettingsViewModel}">
<views:SettingsView DataContext="{Binding}" />
</DataTemplate>
</UserControl.Resources>
<Grid>
</Grid>
</UserControl>
I'll be honest, I think you need to rewind a bit and read a good book on MVVM before continuing. Gary McLean Hall's Pro WPF and Silverlight MVVM is a good place to start.
To answer your question, I'll assume that this user control is set up with its DataContext pointing to your MainViewModel. The content on the right needs a corresponding property in the main view model i.e. something like this:
private ViewModelBase _CurrentPage;
public ViewModelBase CurrentPage
{
get { return this._CurrentPage; }
set
{
if (this._CurrentPage != value)
{
this._CurrentPage = value;
RaisePropertyChanged(() => this.CurrentPage);
}
}
}
You then create a bunch of "pages" or something that inherit ViewModelBase i.e. Page1ViewModel, Page2ViewModel, SettingsViewModel etc. You then create a ContentControl and bind its content to that property:
<ContentControl Content="{Binding CurrentPage}" />
So now if your view model does something like CurrentPage = new SettingsViewModel() then the ContentControl will be populated with whatever you declared as the DataTemplate for that type (i.e. a control of type views:SettingsView). If you assign the property to something else then the SettingsView will be destroyed and replaced by whatever the DataTemplate for the new type is.
In your example above only SettingsViewModel/SettingsView will work, because that's all you've created a DataTemplate for; in order for this to work you need to create a separate DataTemplate for each ViewModel/View pair type you create.
So in Caliburn Micro, I have been using the following method to compose a view inside of another view:
Put a ContentControl inside the composing View.
Create a property on the composing ViewModel, and assign to it the composed ViewModel
Give the ContentControl a x:Name attribute that matches the name of the composed ViewModel property on the composing ViewModel.
like so...
View:
<UserControl x:Class="MyProject.MyComposingView"
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">
<ContentControl x:Name="MyComposedViewModel"/>
</UserControl>
ViewModel:
class ComposingViewModel : PropertyChangedBase
{
private ComposedViewModel _myComposedViewModel;
public ComposedViewModel MyComposedViewModel
{
get { return _myComposedViewModel; }
set
{
_myComposedViewModel= value;
NotifyOfPropertyChange(() => Page);
}
}
public ComposingViewModel(ComposedViewModel myComposedViewModel)
{
MyComposedViewModel = myComposedViewModel;
}
}
Caliburn Micro automagically figures out that because it's a ContentControl it obviously doesn't want to bind to a ViewModel, but rather to its associated View, and so it does something under the hood to bind the ContentControl's Content property to MyComposedView instead of MyComposedViewModel.
But, what if I don't want to use a ContentControl? Like, maybe some reusable custom component of mine that wraps a ContentControl instead? For example:
<UserControl x:Class="MyProject.MyContentWrapper"
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">
<Grid x:Name="PreviewBox" SizeChanged="onSizeChanged">
<Image x:Name="BGImage" Source="{Binding BGImage}"/>
<ContentControl Content="{Binding}"/>
</Grid>
</UserControl>
If I replace the ContentControl with a MyContentWrapper, CaliburnMicro no longer works its magic to supply MyComposedView, and I end up with a TextBlock that says, MyProject.MyComposedViewModel.
How can I get CaliburnMicro to know this is a situation where it should supply the View rather than the ViewModel?
What you want to do is add a convention for your custom control:
Go to the code for ConventionMananger on github.
Search for AddElementConvention<ContentControl>.
Create a new method in your Bootstrapper that runs when your application starts. Add a call to ConventionManager.AddElementConvention<YourControl> similar to the one for ContentControl.
Make sure to put a ContentPropertyAttribute on your control and specify the content property.
Disclaimer: I'm on mobile and can't validate this.
Ok, I'm trying to get to grips with MVVM. I have an application that has multiple options for image capture. Depending on the mode, the image is either loaded from an existing file, or captured from a camera.
I'm writing a page using the MVVM pattern which represents the configuration of the image capture device.
The model consists of two classes which expose the specific (and non common) values for each of the modes which conform to a common interface of IImageSource.
Each of the two model classes have a contextually defined viewmodel:
CameraSourceViewModel
FileSourceViewModel
and two corresponding views.
CameraSourceView
FileSourceView
The model has an attribute which returns IImageSource.
I'm currently using third view, ImageSourceView as the page. I'm handling the loading event which gets the value from the model, then, depending on the type will instantiate the correct viewmodel and the correct view to go with it and then adds that as it's content. However, this seems to be going against the spirit of MVVM in that I've now written some decision code in the code behind
Is there a more elegant/ better way of determining which viewmodel/ view should be instantiated and used?
Actually, you shouldn't need a TemplateSelector, since the two ViewModels will have different types. You can declare DataTemplates in XAML as resources with the model type as key, so that WPF chooses the correct DataTemplate automatically:
Have a main ViewModel, which exposes a ImageSourceViewModel property. This property would either return a CameraSourceViewModel or a FileSourceViewModel, as appropriate.
In your page, the DataContext would be the main ViewModel, and you'd have XAML like this:
Code Example:
<Page x:Class="Page1"
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:my="clr-namespace:WpfApplication1"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300"
Title="Page1">
<Page.Resources>
<DataTemplate DataType="{x:Type my:CameraSourceViewModel}">
<my:CameraSourceView/>
</DataTemplate>
<DataTemplate DataType="{x:Type my:FileSourceViewModel}">
<my:FileSourceView/>
</DataTemplate>
</Page.Resources>
<Grid>
<ContentControl Content="{Binding ImageSourceViewModel}"/>
</Grid>
</Page>
Here's some idea about what you could do:
Have a ImageSourceViewModel, that is the ViewModel of your ImageSourceView view. It would be the role of this viewModel to get "your value" from the model, and expose it as a public property of type IImageSource.
Then, in your ImageSourceView view, you could use a template selector to change the content of the view, depending on the concrete type of the exposed IImageSource property.
See http://www.codeproject.com/Articles/418250/WPF-Based-Dynamic-DataTemplateSelector
You have two palces where you need decide which type to use in run time:
ViewModel
View
On ViewModel level just use ViewModel factory, so just by an EventType/ValueType instantiates an appropriate ViewModel:
private IImageSourceViewModel ProcessEvent(IEvent someEvent)
{
return viewModelFactory.Create(someEvent.Type)
}
Then on View level just use DataTemplateSelector which accepts via binding already resolved ViewModel instance and then decides which View to use:
MainView XAML:
<ContentControl
Content="{Binding ImageSourceViewModel}"
ContentTemplateSelector =
"{StaticResource ImageSourceViewDataTemplateSelector}">
</ContentControl>
ImageSourceViewDataTemplateSelector:
private sealed class ImageSourceViewDataTemplateSelector: DataTemplateSelector
{
public ImageSourceViewDataTemplateSelector(... dependencies if any...)
{
}
public override DataTemplate SelectTemplate(object item, DependencyObject container)
{
DataTemplate dataTemplate = null;
IImageSourceViewModel instance = item as IImageSourceViewModel;
// move out into the constructor
var dataTemplateFactory = new Dictionary<Type, Func<DataTemplate>>
{
{ typeof(ICameraSourceViewModel), (x) => this.Resources["CameraSourceDataTemplate"] as DataTemplate },
{ typeof(IFileSourceViewModel), (x) => this.Resources["FileSourceViewModel"] as DataTemplate }
};
// TODO: handle not supported type case yourself
return dataTemplateFactory[instance.GetType()]();
}
}
I defined an object which contains properties in code-behind and, want to set the object to datacontext in xaml.
namespace WpfApplication4
{
public partial class MainWindow : Window
{
public EquipInfo equip1;
public MainWindow()
{
InitializeComponent();
equip1 = new EquipInfo();
// setting here works fine but i want to do in xaml
//textBox1.DataContext = equip1;
}
}
}
here's xaml code..
<Window x:Class="WpfApplication4.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525">
<Grid>
<TextBox Text="{Binding Path=PIN}" Height="23"
HorizontalAlignment="Left" Margin="172,208,0,0"
Name="textBox1" VerticalAlignment="Top" Width="120" />
</Grid>
</Window>
You can set the DataContext via XAML to a public property. blindmeis has given a good example on how to do this.
What you did in your Post was not setting the DataContext, but binding the Text Property of your Control to a Property of the window. This only works if a DataContext is set. Another way to do that, if you don't want to do MVVM (which I'd recommend, though), is referencing specific Elements in your Binding:
1.) Alter member to AutoProperty or similar
public EquipInfo equip1 {get;set;}
2.) Give your window a name
<Window x:Class="WpfApplication4.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
x:Name="myWin"
Title="MainWindow" Height="350" Width="525">
2.) Access Window Properties by instance name
<TextBox Text="{Binding ElementName=myWin, Path=equip1.PIN}"/>
On a side note, it would really be better to use a MVVM instead.
when you create your equipinfo class in codebehind why not setting the datacontext there?
nevertheless you can create a datacontext in xaml (you did not need the codebehind stuff then)
<TextBox Text="{Binding Path=PIN}">
<TextBox.DataContext>
<local:equip1/>
</TextBox.DataContext>
</TextBox>
but please look at the MVVM pattern
When you create your equipinfo class in codebehind why not setting the datacontext there?
The whole point of MVVM is to seperate UI from code behind using a viewmodel proxy. If you are going to reference the ui element to set its datacontext you might just as well reference it to set its value and forget about mvvm.