When you have a usercontrol in wpf can it reach outside to its parent elements? For instance my user control just lists some generic things which are held inside the control which is encapsulated within a dockpanel on the main window, but I have a textbox and button in the main window that I would like to access from the control... is this possible?
It would save me alot of time rather than changing the content of the entire window and displaying the same textbox/button in every usercontrol. If anyone has an example of this it would be much appreciated.
Yes it is possible and here is some code I have used to compose presentations out of UserControls that have DPs.
I don't love it even a little, but it works. I also think this is a great topic and maybe some code will help get some better answers!
Cheers,
Berry
UserControl XAML
<Button x:Name="btnAddNewItem" Style="{StaticResource blueButtonStyle}" >
<StackPanel Orientation="Horizontal">
<Image Source="{resx:Resx ResxName=Core.Presentation.Resources.MasterDetail, Key=bullet_add}" Stretch="Uniform" />
<Label x:Name="tbItemName" Margin="5" Foreground="White" Padding="10, 0">_Add New [item]</Label>
</StackPanel>
</Button>
UserControl Code Behind
public partial class AddNewItemButton : UserControl
{
...
#region Item Name
public static readonly DependencyProperty ItemNameProperty = DependencyProperty.Register(
"ItemName", typeof(string), typeof(AddNewItemButton),
new FrameworkPropertyMetadata(OnItemNameChanged));
public string ItemName
{
get { return (string)GetValue(ItemNameProperty); }
set { SetValue(ItemNameProperty, value); }
}
public string ButtonText { get { return (string) tbItemName.Content; } }
private static void OnItemNameChanged(DependencyObject obj, DependencyPropertyChangedEventArgs args)
{
// When the item name changes, set the text of the item name
var control = (AddNewItemButton)obj;
control.tbItemName.Content = string.Format(GlobalCommandStrings.Subject_Add, control.ItemName.Capitalize());
control.ToolTip = string.Format(GlobalCommandStrings.Subject_Add_ToolTip, control.ItemName);
}
#endregion
#region Command
public static readonly DependencyProperty CommandProperty = DependencyProperty.Register(
"Command", typeof(ICommand), typeof(AddNewItemButton),
new FrameworkPropertyMetadata(OnCommandChanged));
public ICommand Command
{
get { return (ICommand)GetValue(CommandProperty); }
set { SetValue(CommandProperty, value); }
}
private static void OnCommandChanged(DependencyObject obj, DependencyPropertyChangedEventArgs args)
{
// When the item name changes, set the text of the item name
var control = (AddNewItemButton)obj;
control.btnAddNewItem.Command = control.Command;
}
#endregion
}
Another UserControl showing Composition
<UserControl ...
xmlns:uc="clr-namespace:Smack.Core.Presentation.Wpf.Controls.UserControls"
>
<DockPanel LastChildFill="True">
...
<uc:AddNewItemButton x:Name="_addNewItemButton" Margin="0,0,10 0" DockPanel.Dock="Right" />
...
</DockPanel>
</UserControl>
A better design pattern would be to have the usercontrol notify (via event) the main window when something needs to be changed, and to ask the window (via method) when it needs some information. You would, for example, have a GetText() method on the window that the usercontrol could call, and a ChangeText event on the usercontrol that the window would subscribe to.
The idea is to keep the window in control at all times. Using this mentality will make it easier for you to develop applications in the future.
To answer your question: yes, you can either access parent controls either through a RelativeSource binding or through the Parent member in the back code. But a better answer is similar to #KendallFrey answer. Adopt a framework like Light MVVM and use its messenger class or use events the way Kendall described.
Related
I am working on an application showing 20 graphic buttons controls in a MainWindow (Button1 to Button20).
Each button control can display a Content string, and has a tooltip designed as follow :
<Button x:Name="button1" FontWeight="Bold" FontSize="15" Content="" HorizontalAlignment="Left" Margin="20,69,0,0" VerticalAlignment="Top" Width="92" Height="29" Click="Button_Click" Background="#FFFFFFFF" MouseEnter="button_MouseEnter">
<Button.ToolTip>
<Border Margin="-4,0,-4,-3" Padding="10" Background="Yellow">
<Border.BitmapEffect>
<OuterGlowBitmapEffect></OuterGlowBitmapEffect>
</Border.BitmapEffect>
<Label x:Name ="lbl1" FontSize="20" Content="{Binding Path=ToolTip}">
</Label>
</Border>
</Button.ToolTip>
<Button.Effect>
<DropShadowEffect/>
</Button.Effect>
</Button>
I would like to define the string content and the tooltip string for each button in an XML file so this information can be changed by modifying the XML file.
For this, I created a ViewModel defining an object called Bouton (in french) :
public class Bouton : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
void Notify(string propertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
int boutonNumber;
public int BoutonNumber{ get { return boutonNumber; } set { boutonNumber= value; Notify("BoutonNumber"); } }
string texteBouton;
public string TexteBouton { get { return texteBouton; } set { texteBouton = value; Notify("TexteBouton"); } }
string tooltip;
public string Tooltip { get { return tooltip; } set { tooltip = value; Notify("ToolTip"); } }
public Bouton(int nb, string tb, string tt)
{
BoutonNumber = nb;
TexteBouton = tb;
Tooltip = tt;
}
}
When reading the XML file, I create 20 objects of Bouton type with the information about boutonNumber, Content and Tooltip. Then all these Bouton objects are stored into a List collection.
Now I want to use DataBinding between my Bouton list and the graphic controls on my MainWindow to be able to display the content string and the tooltip string on each button.
In the MainWindow, I used the following code :
public MainWindow()
{
InitializeComponent();
List<Bouton> lst = Utilities.CreateList();
this.DataContext = lst;
}
where lst is a List collection correctly initialized.
But I do not know how to make the databinding work on the Button controls. How can I make each of the 20 Button controls correctly link to the corresponding Bouton object (contained in the Boutons collection) ? I mean how can Button1 control get its strings from my Bouton1 object, the Button2 control get its string from Bouton2 object and so on until Button20 control and Bouton20 object ?
Thank you for your help. Please note I am a beginner with WPF and this is my first WPF project with Visual Studio.
I think the simplest option would be to wrap your button in a UserControl.
This UserControl then contains a property of type Bouton (not great naming btw. - even if it is a different language, the term "button" already exists, so this is rather ambiguous. As a non-native english speaker myself, I also recommend getting used to naming in English, it will save you lots of headaches in the long run, but that might be subjective )
You can then bind directly to that property in the UserControl's template. All you have to do is assign each UserControl the correct button data.
eg.
public class CustomButton : UserControl
{
public Bouton ParsedButtonData { get; set; }
public CustomButton()
{
DataContext = this;
InitializeComponent();
}
}
and in your UserControl's template:
<Button ...>
<Button.ToolTip>
<Border ...>
<Label Content="{Binding ParsedButtonData.Tooltip}" ...>
</Label>
</Border>
</Button.ToolTip>
</Button>
You can then place the UserControl in your XAML like this:
xmlns:ctrls="clr-namespace:MyProject.MyNamespace"
<ctrls:CustomButton name="myFirstButton"/>
Now all you have to do is make sure each CustomButton has their ParsedButtonData set to the corresponding piece of data. You can either set this manually for each button created in your XAML, or you can create the CustomButtons through C# in the first place.
If you create your UserControls in XAML, for example:
public void SomeMethod()
{
myFirstButton.ParsedButtonData = lst[0];
}
Alternatively, you might want to look into extending ItemsControl. It's basically made for this sort of application. An ItemsControl has an ItemsSource, which can be any collection type such as List<> or ObservableCollection<>. It then creates its children from that collection, setting the DataContext automatically to the corresponding element in said list.
Examples of that would be DataGrid or ListView. I find that to be a little more involved though, if you really just want to place a bunch of a Buttons on a single View.
I have a user control and im trying to bind one of its properties
User Control Xaml
<UserControl x:Class="pi_browser.Testing.Example"
...
x: Name="LabelControl">
<StackPanel x:Name="RootStackPanel">
<Label Content="{Binding Text, ElementName=LabelControl}"/>
</StackPanel>
</UserControl>
User Control Codebehind
public partial class Example : UserControl
{
public Example()
{
InitializeComponent();
ExampleViewModel vm = new ExampleViewModel(State);
DataContext = vm;
}
public Boolean State
{
get { return (Boolean)this.GetValue(StateProperty); }
set { this.SetValue(StateProperty, value); }
}
public static readonly DependencyProperty StateProperty =
DependencyProperty.Register("State",
typeof(Boolean), typeof(Example), new PropertyMetadata(false));
}
Main Page View Model
class ExampleViewModel
{
public ExampleViewModel(bool v)
{
val = v;
}
bool val;
public string Text { get => val ? "This worked" : "This didnt work"; }
}
Main Window Xaml
<Window x:Class="pi_browser.Testing.Tester" ... >
<Grid>
<local:Example State="True"/>
</Grid>
</Window>
In this example I didn't bind the State variable, I only passed a literal, but ideally I would like to bind to actual values.
State is a boolean, yet you bind to Text. Let us fix one issue by creating a dependency property Text on your User Control. We shall fix the Text issue and not the boolean State issue. Once you fix that, do the same for State.
So to fix Text we need to fix why this fails:
<Label Content="{Binding Text, ElementName=LabelControl}"/>
You set the ElementName to be the UserControl itself, which is what one wants. But then you tell the binding to look for (remember binding is just reflection of an object under the covers) the property Text. The property Text does not exist on that instance/class...but State does. Its obvious to bind to a newly created Text dependency property on the user control to fix the first issue.
Then when you instantiate the control on your main page, you need to then, and only then bind to Text because that property also resides on your viewmodel.
So three things, along with the change mentioned on the UserControl:
Make your ViewModel adhere to INotifyPropertyChanged and make the Text property use the notification mechanism you install.
Make sure that your main page has its DataContext set to a vailid instance of your ViewModel class.
Bind to Text such as <local:Example State="{Binding Text}"/>
Once that is done, the Text value will properly flow towards the UserControl.
I found a lot of examples on how to bind the IsChecked property of a WPF checkbox to a boolean property, if both belong to the same Window class. I want to do a different thing:
I have the main window (excerpt):
public partial class MainWindow
{
public MainWindow()
{
InitializeComponent();
}
private readonly SoundFx _soundFx = new SoundFx();
private void _StartNewGame()
{
_soundFx.GameStarted();
}
}
Then I have the SoundFx class (excerpt):
public class SoundFx : DependencyObject
{
public void GameStarted()
{
if (Enabled)
{
_PlayGameStartedSound();
}
}
public bool Enabled
{
get { return (bool) GetValue(EnabledProperty); }
set { SetValue(EnabledProperty, value); }
}
public static readonly DependencyProperty EnabledProperty =
DependencyProperty.Register("Enabled", typeof(bool),
typeof(SoundFx), new UIPropertyMetadata(false));
}
And I have the XAML (excerpt):
<Grid>
<CheckBox IsChecked="{Binding ElementName=_soundFx, Path=Enabled}" x:Name="checkBoxSoundFx" Content="Sound FX" HorizontalAlignment="Right" Margin="0,0,10,10" VerticalAlignment="Bottom"/>
</Grid>
To be honest, I'm new to WPF and don't know exactly what I'm doing. What I'm trying to achieve is that the value of _soundFx.Enabled be changed when the user clicks on the checkBoxSoundFx element, without using any event handlers like Checked or Unchecked. This should be possible with data binding, shouldn't it?
First you need to create
public SoundFx _soundFx { get; set; }
as public property, because you cannot bind to private field
public MainWindow()
{
InitializeComponent();
_soundFx = new SoundFx();
}
And from xaml you need to bind like:
<CheckBox IsChecked=
"{Binding RelativeSource=
{RelativeSource Mode=FindAncestor,AncestorType=Window},
Path=_soundFx.Enabled}"}"
x:Name="checkBoxSoundFx"
Content="Sound FX"
HorizontalAlignment="Right"
Margin="0,0,10,10"
VerticalAlignment="Bottom"/>
You were close, you need a property to bind to and you need to set the DataContext if you didn't do it:
public partial class MainWindow
{
public MainWindow()
{
this.DataContext = this;
InitializeComponent();
}
private readonly SoundFx _soundFx = new SoundFx();
public SoundFx {get {return _soundFx;}}
private void _StartNewGame()
{
_soundFx.GameStarted();
}
}
You then need to bind to this property (and set the mode to OneWayToSource if you only need to set the property, never update the CheckBox according to the property value):
<Grid>
<CheckBox IsChecked="{Binding Path=SoundFx.Enabled, Mode=OneWayToSource}" x:Name="checkBoxSoundFx" Content="Sound FX" HorizontalAlignment="Right" Margin="0,0,10,10" VerticalAlignment="Bottom"/>
</Grid>
By the way I'm not sure why you SoundFx is a DependencyObject and why your Enabled property is a DependencyProperty. A simple property would work aswell in this particular example.
DependencyProperties are useful when you want to set them in a Style or animate them with a Storyboard for example, you don't seem to be in this case. I think SoundFx should inherit DependencyObject and Enabled should be a simple property (This is an opinion I make knowing very little about your project though).
As I've managed to grow more experienced in WPF in the meantime, I would now say that my question itself was wrong. In order to avoid confusion in binding and unnecessary dependencies between view and model, I would now always prefer MVVM for cases like this.
Example: https://codereview.stackexchange.com/questions/124361/mvvm-am-i-doing-it-right
I have an UserControl with a button inside. This button needs to add some items to a Grid that's inside said UC. I'm aware I can do this with a Click event.
The issue here is I am using MVVM and altering data outside their corresponding ViewModel would break the format (So to say).
Is there a way to create an ICommand Dependency Property so I can bind said DP to the button and have the functionality of adding the item to the Grid in my ViewModel? (I already have the List in both my UC and my ViewModel and they are working as expected)
Thank you.
Found a way to solve it in the way I was trying to. Leaving the answer here so people may use it:
1) In your User Control's code-behind, create a Dependency Property. I choose ICommand, since in my ViewModel I set it as a DelegateCommmand:
public static readonly DependencyProperty CommandProperty =
DependencyProperty.Register(
"Command",
typeof(ICommand),
typeof(UserControl));
public ICommand Command
{
get
{
return (ICommand)GetValue(CommandProperty);
}
set
{
SetValue(CommandProperty, value);
}
}
2) In your UserControl's XAML code, bind this Dependency Property (In this case, a button):
<Grid DataContext="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type UserControl}}}">
<Button Command="{Binding Command}" />
</Grid>
3) Next, on your ViewModel, declare a Command property and configure accordingly:
public ICommand ViewModelCommand { get; set; }
public ViewModelConstructor()
{
ViewModelCommand = new DelegateCommand(ViewModelCommandExecute);
}
private void ViewModelCommandExecute()
{
// Do something
}
4) Finally, on your View where the UserControl is nested, we declare the binding:
<UserControls:UserControl Command={Binding ViewModelCommand}/>
This way, the binding will take place and you can bind Commands from the buttons of any User Control to your ViewModels without breaking MVVM.
The basic way is to create an Object (ie MyCommand) which implements ICommand, and nest it inside your ViewModel. Inside MyCommand you have no access to your ViewModel. You can workaround it (ie pass a reference to the ViewModel in MyCommand constructor) but at the end it's too much code (for simple stuff like this). I think almost nobody really do this.
Most use a DelegateCommand which resolve (most of) the above issues.
Last but not least, just use event handlers.
If you code them simply like this:
void Grid_MouseMove(object sender, MouseEventArgs e)
{ viewModel.SaveMousePosition(e.GetPosition()); }
you are not breaking any MVVM rule.
And you can't handle the above event with Commands.
There is no Command for MouseMove (there is none for most events), and you can't pass event parameters in a Command.
You can handle every event using Interaction.Triggers like this
But you still miss the capability to handle event parameters (and add ugly XAML).
To me, until WPF will support databinding in event handlers, like
Grid MouseMove="{Binding SaveMousePosition(e)}"
code behind is still the most effective way to handle events.
I faced similar problem and this question/answers helped me the most; so I will post my solution here in case somebody else will google it later. Made with mvvm light.
I had a custom winforms control as a Model and a WPF control as a View. So, xaml of View (I have an usercontrol for my View, no app.xaml):
<UserControl.Resources>
<ResourceDictionary>
<viewModel:ViewModelLocator x:Key="Locator" />
</ResourceDictionary>
</UserControl.Resources>
<UserControl.DataContext>
<Binding Path = "Main" Source="{StaticResource Locator}"></Binding>
</UserControl.DataContext>
<Grid>
<Button Command="{Binding Zoom, ElementName=Wrapper}"></Button>
<viewModel:ProfileWrapper x:Name="Wrapper" >
</viewModel:ProfileWrapper>
</Grid>
Click of a Button is routed to a RelayCommand Zoom in ProfileWrapper (which is where my Model implemented)
Then the xaml of ProfileWrapper is straghtforward:
<Grid>
<WindowsFormsHost>
<local:ManualControl x:Name="abc" ></local:ManualControl>
</WindowsFormsHost>
</Grid>
And the codebehind of ProfileWrapper :
public partial class ProfileWrapper : UserControl
{
public ProfileWrapper()
{
InitializeComponent();
test = abc;
Command = new RelayCommand(() => test.bZoomIn());
}
public ManualControl test;
public RelayCommand Zoom { get; set; }
public static readonly DependencyProperty CommandProperty =
DependencyProperty.Register(
"Zoom",
typeof(ICommand),
typeof(ProfileWrapper));
public ICommand Command
{
get
{
return (ICommand)GetValue(CommandProperty);
}
set
{
SetValue(CommandProperty, value);
}
}
}
My MainViewModel class is empty and all fuctionality goes to ProfileWrapper class, which might be bad, but at least it works.
I have created the following User Control:
<UserControl x:Class="TextBinder" ...>
<TextBox Text="{Binding ????}" />
</UserControl>
Now I am using my user control twice in my MainWindow. The MainWindow is then bound to my ViewModel (I set the DataContext). Now the problem is: how can I bind my user controls to the user_controlViewModel?
In my ViewModel, I have created two objects let's call them UC_1 and UC_2, they contain different texts and I would like to bind them to their respective user control in my MainWindow.
What should I put at ????
Note: please do not simplify mu TextBox to double textboxes in one usercontrol. This is not what I would like since in my real life example I have more stuff than textbox only and the usercontrol should be used multiple times in one view.
Thanks!
i gave you a general answer:
within a "real(a usercontrol you wanna use with different viewmodels with different property names)" usercontrol you bind just to your own DependencyProperties and you do that with ElementName or RelativeSource binding and you should never set the DataContext within a UserControl.
<UserControl x:Name="myRealUC" x:class="MyUserControl">
<TextBox Text="{Binding ElementName=myRealUC, Path=MyOwnDPIDeclaredInMyUc, Path=TwoWay}"/>
<UserControl>
if you do that you can easily use this Usercontrol in any view like:
<myControls:MyUserControl MyOwnDPIDeclaredInMyUc="{Binding MyPropertyInMyViewmodel}"/>
and for completeness: the Dependency Property
public readonly static DependencyProperty MyOwnDPIDeclaredInMyUcProperty = DependencyProperty.Register(
"MyOwnDPIDeclaredInMyUc", typeof(string), typeof(MyUserControl), new PropertyMetadata(""));
public bool MyOwnDPIDeclaredInMyUc
{
get { return (string)GetValue(MyOwnDPIDeclaredInMyUcProperty); }
set { SetValue(MyOwnDPIDeclaredInMyUcProperty, value); }
}
Thats right, you need yo declare a dependency property in your UserControl:
public partial class TextBinder:UserControl
{
public static readonly DependencyProperty textproperty =
DependencyProperty.Register("Text", typeof(string), typeof(TextBinder));
public string Text
{
get
{
return this.GetValue(textproperty) as string;
}
set
{
this.SetValue(textproperty,value);
}
}
}
And then, you can use your usercontrol in your window at this way:
<YourNamespace:TextBinder Text={Binding ViewModelProperty}/>