I am using the convention-based binding from Caliburn.Micro, but I have a small issue:
How do I set the property that my binding should bind to? If I create a control with x:Name="SomeProperty", how do I choose if the value of SomeProperty should be binded to the Value Property of my control, the OnClick Event of my control or something different, like the Content or the Tag property?
Example: I have this HyperlinkButton that I want to bind to a specific URL, and I want to bind the OnClick to an event handler in my ViewModel.
<HyperlinkButton x:Name="BookDetailsViewModel_InfoLink" Content="Read more" />
The Content property however is not filled with Read more but with the value of the URL. In this example, how do I:
Set the navigation URI to the value of the URL in my ViewModel property
Set the content to "Read more"
Specify an event handler in my ViewModel that will handle the click
Can anyone help me please?
You can customise the ConventionManager per element type in CM. The default out-of-box implemententation is applied to any element which doesn't have an explicit customisation
To add a new convention you just simply call ConventionManager.AddElementConvention
The method looks like this (from CM source)
/// <summary>
/// Adds an element convention.
/// </summary>
/// <typeparam name="T">The type of element.</typeparam>
/// <param name="bindableProperty">The default property for binding conventions.</param>
/// <param name="parameterProperty">The default property for action parameters.</param>
/// <param name="eventName">The default event to trigger actions.</param>
public static ElementConvention AddElementConvention<T>(DependencyProperty bindableProperty, string parameterProperty, string eventName)
{
return AddElementConvention(new ElementConvention
{
ElementType = typeof(T),
GetBindableProperty = element => bindableProperty,
ParameterProperty = parameterProperty,
CreateTrigger = () => new EventTrigger { EventName = eventName }
});
}
As you can see, it takes a few args - you need to pass the default property for bindings, actions, and triggers e.g.
ConventionManager.AddElementConvention<HyperlinkButton>(HyperlinkButton.NavigateUri, "NavigateUri", "Click");
(assuming the click event is called Click)
Since you aren't binding the Content property any more (because the convention is to now bind NavigateUri) you can just leave that as-is and it should remain 'Read more...'
So now you have a HyperlinkButton control which should bind by convention to the NavigateUri, and call the method which shares it's name when the Click event is triggered.
Edit:
I might make a clarification that I don't think you can bind to both a method and a property on the same VM since you can't have a method and a property that share the same name, but I'm sure CM would bubble the action message up the VM hierarchy if you didn't have the appropriate method on the VM... not tried it though. To bind the actions see my other edit below
Don't forget, you could always just use the explicit syntax for all of this!
<HyperlinkButton Content="Read more..." NavigationURI="{Binding SomeUri}" cal:Message.Attach="[Event Click] = [Action HyperlinkClicked($this.NavigateUri)" />
but it's probably better to go the convention route :)
Edit:
Might add how to get convention to grab the property value from the hyperlink -
<HyperlinkButton x:Name="SomeLink" Content="Read more..." cal:Message.Attach="HyperlinkClicked(SomeLink)" />
CM knows that since you set NavigateUri as the default action parameter, it should grab this and pass it to the method that you specified in the action binding. I'm wondering if $this will also work (you probably would need $this.NavigateUri). You can do this across controls e.g.
<TextBox x:Name="SomeTextBox" />
<HyperlinkButton x:Name="SomeLink" Content="Read more..." cal:Message.Attach="HyperlinkClicked(SomeTextBox)" />
The above would pass the Text property of the textbox to the HyperlinkClicked method by default.
Related
I have a problem with Xamarin.Forms.EntryCell. I want to know how to trigger an event if the text of the EntryCell has changed. Not the event 'Completed', it is just triggered when I press Enter after inputting anything.
<EntryCell
Label="User Id"
x:Name="UserIdEntryCell"
HorizontalTextAlignment="End"
Completed="UserIdCompleted"/>
Obviously there is no event that is fired when the text is changed (see the docs). Anyway, this does not mean that you can't achieve what you want, by means of Text property. Since you are using the event I do not assume that you use MVVM (you should really give it a try, though), hence we'll have to create a property we will bind the EntryCell.Text to in your view (I'm assuming a view, but for a page it would be quite similar)
In your code behind add a property Text that calls HandleTextChanged from its setter:
class MyView : ContentView
{
string _text;
public string Text
{
get => _text;
set
{
_text = value;
HandleTextChanged();
}
}
private void HandleTextChange()
{
// do whatever you need to do
}
}
You can bind this property to EntryCell.Text from your XAML
Now MyView.Text will be set every time the text in your EntryCell changes.
I have an <Entry> in xaml, and I want to get that value the user types.
<Entry x:name="enteredInput>
The file with that <Entry> is in startingPage.xaml with a code behind class startingPage.xaml.cs.
Then I would like to transfer that value in the <Label> element of a different xaml, MainPage.xaml.
In your second page, add another constructor with string parameter.
For ex, If your page name is StartingPage.xaml, then add another constructor like below. Inside, assign the incoming value to your label.
public StartingPage(string entryTextFromStartingPage)
{
InitializeComponent();
lblEntryTextDisplay.Text = entryTextFromStartingPage;
}
From the StartingPage.xaml.cs, add the below code in a button click or any event that you are calling the Main page,
Navigation.PushAsync(new MainPage(enteredInput.Text);
I would like to set the style of my dependent on the value of its "opened" attribute.
To be more specific: if the value of opened==false I would like to hide the simpleTogglePanel on printouts (i.e. setting style to display:none).
so something like this (pseudo code):
<rich:simpleTogglePanel opened="false" styleClass="#{ if opened then regular else hidePrint}" />
Is this possible? How? I'm using Richfaces 3.3.2.!
Use rendered attribute of your component.
<rich:simpleTogglePanel rendered=#{bean.boolean} />
EDIT
You should have a boolean property in your managed bean, so you will know if it's your togglePanel opened or not. So something like
#ManagedBean
#RequestScoped
public class Bean {
private boolean opened;
//setters and getters
}
then on your page change your togglePanel like this
<rich:simpleTogglePanel opened="#{bean.opened}" rendered="#{bean.opened}">
set the property in your bean to true or false depending on if you want to hide your togglePanel defaultly. Or you can hide it everytime when it's get toggled with Ajax, put this line inside the simpleTogglePanel tag
<p:ajax listener="#{bean.hidePanel}" update=":panel" />
set id of your panel to panel and add method hidePanel to your panel which just sets the boolean opened to false. Edit - it also should work without that listener
I created a class CustomCombo.as that extends ComboBox. What is happening is that the CustomCombo combobox is showing as being editable. I do not want this and I cant find the properties to set the editable to false.
I also tried setting the combobox's textInput.editable control to false, but to no avail.
Any help would be greatly appreciated.
CustomCombo.as
package custom {
import spark.components.ComboBox;
public class CustomCombo extends ComboBox {
public function CustomCombo() {
super();
// this.editable = false; //<-- THIS DOESNT WORK ***Access of possibly undefined property editable through a reference with static type custom:CustomCombo
// this.textInput.editable = false; //<-- THIS DOESNT WORK ***Cannot access a property or method of a null object reference
}
}
}
After rummaging through the Flex 4 API I found that they suggest to use the DropDownList control. From what I can see is that they removed the editable property from the ComboBox control in Flex 4, but I may be wrong.
I implemented DropDownList and that solved my problem.
I see that you're using spark and not mx. The editable property I referred to is applicable only to mx based list. In spark, ComboBox extends DropDownListBase and is editable by default.
The ComboBox control is a child class of the DropDownListBase control. Like the DropDownListBase control, when the user selects an item from the drop-down list in the ComboBox control, the data item appears in the prompt area of the control.
One difference between the controls is that the prompt area of the ComboBox control is implemented by using the TextInput control, instead of the Label control for the DropDownList control. Therefore, a user can edit the prompt area of the control to enter a value that is not one of the predefined options.
For example, the DropDownList control only lets the user select from a list of predefined items in the control. The ComboBox control lets the user either select a predefined item, or enter a new item into the prompt area. Your application can recognize that a new item has been entered and, optionally, add it to the list of items in the control.
The ComboBox control also searches the item list as the user enters characters into the prompt area. As the user enters characters, the drop-down area of the control opens. It then and scrolls to and highlights the closest match in the item list.
So ideally, you should be using DropDownList in this case.
You're getting null error when trying to access textInput from the constructor because it hasn't been created yet. In mx based controls (Flex-3), you can access it from the creationComplete handler; I'm not quite sure how to do it for spark based controls.
Update: I think I've figured out how to access skin parts in spark (though you might wanna use the DropDownBox instead). You have to override the partAdded method.
override protected function partAdded(partName:String, instance:Object):void
{
super.partAdded(partName, instance);
if (instance == textInput)
{
textInput.editable = false;
}
}
There's one catch though: it may not work in this case. The source code of ComboBox.as says that
the API ignores the visual editable and selectable properties
So DropDownList it is!
Initial answer, posted for mx ComboBox.
This shouldn't happen as the default value of the editable property is false.
Try explicitly setting the value to false from the constructor.
public function CustomCombo() {
super();
this.editable = false;
}
My generic question is as the title states, is it best to load data during ViewModel construction or afterward through some Loaded event handling?
I'm guessing the answer is after construction via some Loaded event handling, but I'm wondering how that is most cleanly coordinated between ViewModel and View?
Here's more details about my situation and the particular problem I'm trying to solve:
I am using the MVVM Light framework as well as Unity for DI. I have some nested Views, each bound to a corresponding ViewModel. The ViewModels are bound to each View's root control DataContext via the ViewModelLocator idea that Laurent Bugnion has put into MVVM Light. This allows for finding ViewModels via a static resource and for controlling the lifetime of ViewModels via a Dependency Injection framework, in this case Unity. It also allows for Expression Blend to see everything in regard to ViewModels and how to bind them.
So anyway, I've got a parent View that has a ComboBox databound to an ObservableCollection in its ViewModel. The ComboBox's SelectedItem is also bound (two-way) to a property on the ViewModel. When the selection of the ComboBox changes, this is to trigger updates in other views and subviews. Currently I am accomplishing this via the Messaging system that is found in MVVM Light. This is all working great and as expected when you choose different items in the ComboBox.
However, the ViewModel is getting its data during construction time via a series of initializing method calls. This seems to only be a problem if I want to control what the initial SelectedItem of the ComboBox is. Using MVVM Light's messaging system, I currently have it set up where the setter of the ViewModel's SelectedItem property is the one broadcasting the update and the other interested ViewModels register for the message in their constructors. It appears I am currently trying to set the SelectedItem via the ViewModel at construction time, which hasn't allowed sub-ViewModels to be constructed and register yet.
What would be the cleanest way to coordinate the data load and initial setting of SelectedItem within the ViewModel? I really want to stick with putting as little in the View's code-behind as is reasonable. I think I just need a way for the ViewModel to know when stuff has Loaded and that it can then continue to load the data and finalize the setup phase.
Thanks in advance for your responses.
For events you should use the EventToCommand in MVVM Light Toolkit. Using this you can bind any event of any ui element to relaycommand. Check out his article on EventToCommand at
http://blog.galasoft.ch/archive/2009/11/05/mvvm-light-toolkit-v3-alpha-2-eventtocommand-behavior.aspx
Download the sample and have a look. Its great. You won't need any codebehind then. An example is as follows:
<Page x:Class="cubic.cats.Wpf.Views.SplashScreenView"
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:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"
xmlns:cmd="clr-namespace:GalaSoft.MvvmLight.Command;assembly=GalaSoft.MvvmLight.Extras"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300"
Title="SplashScreenPage">
<i:Interaction.Triggers>
<i:EventTrigger EventName="Loaded">
<cmd:EventToCommand Command="{Binding LoadedCommand}" />
</i:EventTrigger>
</i:Interaction.Triggers>
<Grid>
<Label Content="This is test page" />
</Grid>
</Page>
and the view mode could be like this
public class SplashScreenViewModel : ViewModelBase
{
public RelayCommand LoadedCommand
{
get;
private set;
}
/// <summary>
/// Initializes a new instance of the SplashScreenViewModel class.
/// </summary>
public SplashScreenViewModel()
{
LoadedCommand = new RelayCommand(() =>
{
string a = "put a break point here to see that it gets called after the view as been loaded";
});
}
}
if you would like the view model to have the EventArgs, you can simple set PassEventArgsToCommand to true:
<i:Interaction.Triggers>
<i:EventTrigger EventName="Loaded">
<cmd:EventToCommand PassEventArgsToCommand="True" Command="{Binding LoadedCommand}" />
</i:EventTrigger>
</i:Interaction.Triggers>
and the view model will be like
public class SplashScreenViewModel : ViewModelBase
{
public RelayCommand<MouseEventArgs> LoadedCommand
{
get;
private set;
}
/// <summary>
/// Initializes a new instance of the SplashScreenViewModel class.
/// </summary>
public SplashScreenViewModel()
{
LoadedCommand = new RelayCommand<MouseEventArgs>(e =>
{
var a = e.WhateverParameters....;
});
}
}
The following solution is similar to the one already provided and accepted, but it does not use a command in the view model to load the data, but a "normal method".
I think commands are more suited for user actions (commands can be available and not available at runtime), that is why a use a regular method call, but also by setting a an interaction trigger in the view.
I suggest this:
Create a view model class.
Instantiate the view model class within the xaml of the view by creating it inside the DataContext property.
Implement a method to load the data in your view model, e.g. LoadData.
Set up the view, so that this method is called when the view loads.
This is done by an interaction trigger in your view which is linked to the method in the view model (references to "Microsoft.Expression.Interactions" and "System.Windows.Interactivity" are needed):
View (xaml):
<Window x:Class="MyWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Test"
xmlns:viewModel="clr-namespace:ViewModels"
xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
xmlns:ei="http://schemas.microsoft.com/expression/2010/interactions"
>
<Window.DataContext>
<viewModel:ExampleViewModel/>
</Window.DataContext>
<i:Interaction.Triggers>
<i:EventTrigger EventName="Loaded">
<ei:CallMethodAction TargetObject="{Binding}" MethodName="LoadData"/>
</i:EventTrigger>
</i:Interaction.Triggers>
This will call the LoadData method in the ViewModel at runtime when the view is loaded. This is where you load your data.
public class ExampleViewModel
{
/// <summary>
/// Constructor.
/// </summary>
public ExampleViewModel()
{
// Do NOT do complex stuff here
}
public void LoadData()
{
// Make a call to the repository class here
// to set properties of your view model
}
If the method in the repository is an async method, you can make the LoadData method async too, but this is not needed in each case.
By the way, generally I would not load data in the constructor of the view model.
In the example above the (parameter less) constructor of the view model is called when the designer shows your view. Doing complex things here can cause errors in the designer when showing your view (for the same reason I would not make complex things in the views constructor).
In some scenarios code in the view models constructor can even cause issues at runtime, when the view models constructors executes, set properties of the view model which are bound to elements in the view, while the view object is not completely finished creating.
Ok, then. :-)
You can bind to a method in the ViewModel by using a behavior.
Here is a link that will help you with that.
http://expressionblend.codeplex.com/
I decided to just have the XAML declaratively bound to a Loaded event handler on the View's code-behind, which in turn just called a method on the ViewModel object, via the View's root element UserControl DataContext.
It was a fairly simple, straight forward, and clean solution. I guess I was hoping for a way to bind the Loaded event to the ViewModel object in the same declarative way you can with ICommands in the XAML.
I may have given Klinger the official answer credit, but he posted a comment to my question, and not an answer. So I at least gave him a one-up on his comment.
I had this same problem when dealing with messages between a parent window and a child window. Just change the order in which your view models are created in your ViewModelLocator class. Make sure that all view models which are dependent on a message are created before the view model that sends the message.
For example, in your ViewModelLocator class's constructor:
public ViewModelLocator()
{
if (s_messageReceiverVm == null)
{
s_messageReceiverVm = new MessageReceiverVM();
}
if (s_messageSenderVm == null)
{
s_messageSenderVm = new MessageSenderVM();
}
}