I've scoured the Internet but my search-foo must not work if it's out there cause I just can't find the proper search terms to get me the answer I am looking for. So I turn to the experts here as a last resort to point me in the right direction.
What I'm trying to do is create a composite control of a text box and a list box but I want to allow the control consumer to decide what to do when say the checkbox is checked/unchecked and I just can't figure it out... Maybe i'm going about it all wrong.
What I've done thus far is to:
create a custom control that extends ListBox
expose a custom DP named "Text" (for the Text box but it's not the important part)
craft the generic.xaml so that the list items have a default ItemTemplate/DataTemplate
inside the DataTemplate I'm trying to set the "Checked" or "Unchecked" events
expose 'wrapper' events as DPs in the custom control that would get 'set' via the template when instatiated
As soon as I try something like the following (inside generic.xaml):
<DataTemplate>
<...>
<CheckBox Checked="{TemplateBinding MyCheckedDP}"/>
<...>
</DataTemplate>
I get runtime exceptions, the designer - vs2010 - pukes out a LONG list of errors that are all very similar and nothing I do can make it work.
I went so far as to try using the VisualTreeHelper but no magic combination I could find would work nor would it allow me to traverse the tree because when the OnApplyTemplate method fires the listbox items don't exist yet and aren't in the tree.
So hopefully this all makes sense but if not please let me know and I'll edit the post for clarifications.
Thanks to all for any pointers or thoughts... Like I said maybe I'm heading about it in the wrong way from the start...
EDIT (Request for xaml)
generic.xaml datatemplate:
<DataTemplate >
<StackPanel Orientation="Horizontal" >
<local:FilterCheckbox x:Name="chk">
<TextBlock Text="{Binding Path=Display}" />
</local:FilterCheckbox>
</StackPanel>
</DataTemplate>
usercontrol.xaml (invocation of custom control)
<local:MyControl FancyName="This is the fancy name"
ItemChecked="DoThisWhenACheckboxIsChecked" <-- this is where the consumer "ties" to the checkbox events
ItemsSource="{Binding Source={StaticResource someDataSource}}"
/>
Assuiming that you want to fire the event from your Custom Control, while clicking on the checkBox , and handling it in your implementing class,
the following should work.
First of all try to remove the template binding with event, though we expect it may work as it works templateBinding DependencyProperty
So your XAML will look something as below:
<DataTemplate>
<...>
<CheckBox x:Name="myCheckBox" />
<...>
</DataTemplate>
Now You have to access your checkBox from generic.cs as below:
public override void OnApplyTemplate()
{
base.OnApplyTemplate();
CheckBox myCheckBox= GetTemplateChild("myCheckBox") as CheckBox;
}
Now You just need to add a event to your Control.
public event EventHandler checkBoxChecked;
Now this are typically implemented like this:-
protected virtual void onCheckBoxChecked(EventArgs e)
{
var handler = checkBoxChecked;
if (handler != null)
handler(this, e);
}
Now in your existing checked events:-
myCheckBox.Checked+= (obj, Args) =>
{
onCheckBoxChecked(EventArgs.Empty);
}
Now in your consuming code or Implementing Application you can do this sort of thing(Assuming your CustomControl Name is myCustomControl):-
myCustomControl.checkBoxChecked+=(s, args) => { /* Your Code Here*/ };
Hope this is what you were trying to acomlish. If need anything esle , letz discuss here.
Update
Well there is detailed discussion about onApplyTemplate and loading the controls and accessing the events outside that class:
Have a look:
How to Access a Button present inside a Custom Control, from the implementing page?
Well I dislike answering my own question but the answer above doesn't work for me nor does the link referenced do what i'm looking for.
I'm posting this only answer so that i can maintain the 100% answer rate...
Related
I have read some thread about how to work on WPF ListView command binding.
Passing a parameter using RelayCommand defined in the ViewModel
Binding Button click to a method
Button Command in WPF MVVM Model
How to bind buttons in ListView DataTemplate to Commands in ViewModel?
All of them suggest write the logic code inside ViewModel class, for example:
public RelayCommand ACommandWithAParameter
{
get
{
if (_aCommandWithAParameter == null)
{
_aCommandWithAParameter = new RelayCommand(
param => this.CommandWithAParameter("Apple")
);
}
return _aCommandWithAParameter;
}
}
public void CommandWithAParameter(String aParameter)
{
String theParameter = aParameter;
}
It is good practice or anyway so I can move the CommandWithAParameter() out of the ViewModel?
In principle, MVVM application should be able to run to its full potential without creating the views. That's impossible, if some parts of your logic are in View classes.
On top of that, ICommand has CanExecute, which will autamagically disable buttons, menu items etc. if the command should not be run.
I understand why with basic RelayCommand implementation it can be hard to see the benefits, but take a look at ReactiveCommand samples.
ReactiveCommand handles async work very well, even disabling the button for the time work is done and enabling it afterwards.
Short example: you have a login form. You want to disable the login button if the username and password are empty.
Using commands, you just set CanExecute to false and it's done.
Using events, you have manualy disable/enable the button, remember that it has to be done in Dispatcher thread and so on - it gets very messy if you have 5 buttons depending on different properties.
As for ListView, commands are also usefull - you can bind current item as command parameter:
<ListView ItemsSource="{Binding MyObjects}">
<ListView.ItemTemplate>
<DataTemplate>
<DockPanel>
<!-- change the context to parent ViewModel and pass current element to the command -->
<Button DockPanel.Dock="Right" Command="{Binding ElementName=Root, Path=ViewModel.Delete}" CommandParameter="{Binding}">Delete</Button>
<TextBlock Text="{Binding Name}"/>
</DockPanel>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
I am fairly new to WPF, have been working on finding an answer to this for a couple days without much luck, it seems like there should be a way. I have set up a DataTemplate whose DataType is a custom class of mine. Within the DataTemplate definition, I have set up a resources collection using . I did this because I want to create an ObjectDataProvider that will be available to the controls in the DataTemplate - I want the ObjectInstance of this ObjectDataProvider, to be currently bound data item (teh current instance within a list, of my custom class) - because then I want to be able to run a method on the current data instance - when the user changes the text in a textbox that is part of the DataTemplate. Hard to explain but this should make it clearer, here is my xaml:
<DataTemplate x:Key="TierDisplay" DataType="{x:Type tiers:PopulatedTier}">
<DataTemplate.Resources>
<ObjectDataProvider x:Key="FilteredItems" MethodName="GetDisplayItems">
<ObjectDataProvider.MethodParameters>
<sys:Int32>0</sys:Int32>
</ObjectDataProvider.MethodParameters>
</ObjectDataProvider>
</DataTemplate.Resources>
<StackPanel Orientation="Vertical">
<TextBox Name="txtMaxSupplyDays" LostFocus="txtMaxSupplyDays_LostFocus"></TextBox>
<DataGrid ItemsSource="{Binding Source={StaticResource FilteredItems}}" />
</StackPanel>
</DataTemplate>
Each instance of the DataTemplate is bound to an instance of the PopulatedTier class. When the user leaves the textbox, txtMaxSupplyDays, I have code in the code-behind to take the value they have entered, and put it into the first MethodParameter of my ObjectDataProvider (whose key is FilteredItems). This works fine using the C# code-behind below, the code finds FilteredItems and plugs the desired value into the MethodParameter. But I can't figure how to tie FilteredItems into the current instance of PopulatedTier so that its GetDisplayItems will run. (If this worked, then presumably the DataGrid would refresh, using the output of GetDisplayItems as its ItemsSource.) In fact, in the C# below, it finds/recognizes the DataContext property of the textbox (sender) as being an instance of PopulatedTier. But how can I refer to this in the XAML within the ObjectDataProvider definition? THANK YOU and let me know if I can clarify further. Of cousre alternate suggestions are welcome; I'd like to keep as much in the XAML and out of the code-behind as I can.
private void txtMaxSupplyDays_LostFocus(object sender, RoutedEventArgs e)
{
var textBox = sender as TextBox;
if (textBox == null) return;
int value;
bool valueOK = Int32.TryParse(textBox.Text, out value);
if (valueOK)
((ObjectDataProvider)textBox.FindResource("FilteredItems")).MethodParameters[0] = value;
}
You have right thoughts about your code-behind - it have to be as small as possible. Its one of the slogan of MVVM pattern, that is what you need - learn MVVM. Internet have a lot of resources, so it wouldn't be a problem to find it.
I am trying to figure out the best way to select all the text in a TextBox the first time the control is loaded. I am using the MVVM pattern, so I am using two-way binding for the Text property of the TextBox to a string on my ViewModel. I am using this TextBox to "rename" something that already has a name, so I would like to select the old name when the control loads so it can easily be deleted and renamed. The initial text (old name) is populated by setting it in my ViewModel, and it is then reflected in the TextBox after the data binding completes.
What I would really like to do is something like this:
<TextBox x:Name="NameTextBox" Text="{Binding NameViewModelProperty, Mode=TwoWay}" SelectedText="{Binding NameViewModelProperty, Mode=OneTime}" />
Basically just use the entire text as the SelectedText with OneTime binding. However, that does not work since the SelectedText is not a DependencyProperty.
I am not completely against adding the selection code in the code-behind of my view, but my problem in that case is determining when the initial text binding has completed. The TextBox always starts empty, so it can not be done in the constructor. The TextChanged event only seems to fire when a user enters new text, not when the text is changed from the initial binding of the ViewModel.
Any ideas are greatly appreciated!
Dan,
I wrote a very simple derived class, TextBoxEx, that offers this functionality. The TextBoxEx class derives from TextBox, and can be referenced in XAML for any and all of your TextBox’s. There are no methods to call. It just listens for Focus events and selects it own text. Very simple.
Usage is as follows:
In XAML, reference the assembly where you implement the TextBoxEx class listed below, and add as many TextBoxEx elements as you need. The example below uses data binding to display a username.
<UserControl x:Class="MyApp.MainPage"
xmlns="http://schemas.microsoft.com/client/2007"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:c="clr-namespace:ClassLibrary;assembly=ClassLibrary"
>
.
.
.
<c:TextBoxEx x:Name="NameTextBox" Text="{Binding NameViewModelProperty, Mode=TwoWay}" Width="120" />
This code below works with Silverlight 3.
using System.Windows;
using System.Windows.Controls;
namespace ClassLibrary
{
// This TextBox derived class selects all text when it receives focus
public class TextBoxEx : TextBox
{
public TextBoxEx()
{
base.GotFocus += OnGotFocus;
}
private void OnGotFocus(object sender, RoutedEventArgs e)
{
base.SelectAll();
}
}
}
Good luck.
I'm leaving Jim's solution as the answer, since calling SelectAll() on the GotFocus event of the TextBox did the trick.
I actually ended up making a Blend TriggerAction and an EventTrigger to do this instead of subclassing the TextBox or doing it in code-behind. It was really simple to do and nice to be able to keep the behavior logic encapsulated and just add it declaratively in XAML to an existing TextBox.
Just posting this in case anyone else comes across this thread and is interested:
XAML:
<TextBox x:Name="NameTextBox" Text="{Binding NameViewModelProperty, Mode=TwoWay}">
<i:Interaction.Triggers>
<i:EventTrigger EventName="GotFocus">
<local:SelectAllAction/>
</i:EventTrigger>
</i:Interaction.Triggers>
</TextBox>
C#
public class SelectAllAction : TriggerAction<TextBox>
{
protected override void Invoke(object parameter)
{
if (this.AssociatedObject != null)
{
this.AssociatedObject.SelectAll();
}
}
}
Just wanna add a link I found pertaining to this - here is a fantastic discussion (read comments) on Behaviours vs subclassing vvs attached properties...
I'm learning MVVM through a project and I got stuck on something simple.
I have a Button that updates a ListView. I have a command in the ViewModel that make the right things but I want to select the new row and get the focus on a TextBox after I click the Button.
The question is: How do I update my UI after executing a command?
If I need to change my windows Title when an operation have been made, I use a property on the ViewModel that is binded to the Window's title and it's changed when I need it but, I don't know how to get focus on a control when a command has been executed.
Thank you.
To select the new row, add a new property to your ViewModel ("SelectedItem" for instance), and bind the ListView's SelectedItem property to it :
<ListView ItemsSource="{Binding Items}" SelectedItem="{Binding SelectedItem}">...
In the ViewModel, you just have to assign the new item to the SelectedItem property
To focus the TextBox, Mike's idea seems a good one
You could make an attached behavior. I'd suggest using the new Blend behavior framework, ie TriggerAction that contained this custom logic.
For your attached behavior you put on the button, give it a DP for an ICommand and maybe a DP of a ListView type.
On the "protected override void Invoke(object parameter)" of your TriggerAction, execute your ICommand, then you have reference to your ListView. Here you can do your custom code on it, like setting focus.
Your XAML may look something like this:
<Button>
<i:Interaction.Triggers>
<i:EventTrigger EventName="Click">
<Behaviors:CustomBehavior Command="CommandName" ListView="{Binding ElementName=myListView}" />
</i:EventTrigger>
</i:Interaction.Triggers>
<Button/>
I suggest looking at Mike Brown's ExecuteCommandAction behavior (download here), it's about almost 1/2 of what you need.
What about setting focus to the control in the code behind: textBox.Focus()
I consider everything you mention in your question to be GUI logic so I would add a Click event to the button to handle stuff that needs to happend in the GUI.
Hope this helps.
I think you need to use the Mediator pattern. Please see this:
Josh Smith's Mediator Prototype for WPF Apps
This is generally used in communicating with the View from the View-Model. Hope this helps.
In your case you need some way that the ViewModel notifies the View that it should set the focus on a specific control.
This could be done with an IView interface. The view implements this interface and the ViewModel can call a method of the View over this interface. This way you have still the View and ViewModel decoupled of each other.
How this can be done is shown here:
WPF Application Framework (WAF)
http://waf.codeplex.com
I have two projects. One is working and the other isn't however the differences between them is nothing that I think "should" be of any importance. The first project is the one that is broken and it is the one I am trying to fix. The second project is a little sample project that I created when the first project just won't work at all. Of course the sample works perfectly.
Here is the view for the first project. I have removed a bunch of the "MainWindowTabControlStyle" because it is just the combo box that is broken. I am reasonable certain that the issue is not in the style because it is a copy and paste from the project that is working.
<Grid>
<TabControl Style="{DynamicResource MainWindowTabControlStyle}">
<TabItem Header="Tab 1"/>
<TabItem Header="Tab 2"/>
</TabControl>
</Grid>
<Style x:Key="MainWindowTabControlStyle" TargetType="{x:Type TabControl}">
...
<ComboBox
HorizontalAlignment="Right"
VerticalAlignment="Top"
IsSynchronizedWithCurrentItem="True"
ItemsSource="{Binding Path=Subscriptions, Mode=Default}"
SelectedItem="{Binding Path=SelectedSubscription, Mode=OneWayToSource}"
ItemTemplate="{DynamicResource SubscriptionsItemTemplate}"/>
...
</Style>
<DataTemplate x:Key="SubscriptionsItemTemplate">
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Path=DisplayName, Mode=Default}"/>
</StackPanel>
</DataTemplate>
Here is the view model that is set to the DataContext of the MainWindow. The ViewModelBase class is the exact same code that Josh Smith wrote in this months MSDN article.
public sealed class MainWindowViewModel : ViewModelBase
{
public MainWindowViewModel()
{
}
private ObservableCollection<Subscription> subscriptions;
public ObservableCollection<Subscription> Subscriptions
{
get
{
if (subscriptions == null)
{
subscriptions = new ObservableCollection<Subscription>();
subscriptions.Add(new Subscription() { DisplayName = "ABC" });
subscriptions.Add(new Subscription() { DisplayName = "XYZ" });
subscriptions.Add(new Subscription() { DisplayName = "PDQ" });
}
return subscriptions;
}
set { subscriptions = value; }
}
private Subscription selectedSubscription;
public Subscription SelectedSubscription
{
get { return selectedSubscription; }
set { selectedSubscription = value; }
}
}
When I run the project from the debugger the first think that is called is the getter for the Subscriptions collection. Then the setter is called on the SelectedSubscription (it is null). After that I can change the selected item in the combobox till I am blue in the face and the setter for the SelectedSubscription property doesn't get changed again. It is important to note that the combobox does contain the correct values.
In the second project the code is identical but the first thing that is called is the setter for the SelectedSubscription property (it is null) then the getter for the Subscriptions collection is called and finally the setter for the SelectedSubscription is called a second time and it has a value that matches the first item in the Subscriptions collection.
This little jewel has cost me about 5 hours if you have any ideas at all I am willing to try it.
Thanks
Possibly change
SelectedItem="{Binding Path=SelectedSubscription, Mode=OneWayToSource}"
to
SelectedItem="{Binding Path=SelectedSubscription, Mode=TwoWay}"
Sorry about the delay in getting an answer posted. There was some kind of issue with getting an Open ID up and running.
This is a seriously weird issue.
The resolution to this problem didn't come from the window at all. Prior to the window's show method being called there was another window that was opened as a dialog. In this dialog there was the following resource
<Window.Resources>
<DropShadowBitmapEffect x:Key="DropShadowEffect" Noise="0" Opacity="0.45" ShadowDepth="5" Softness="0.25"/>
</Window.Resources>
It was was being referenced by two textblocks in the same window as a "DynamicResource". After turning off the dialog and making the application start with the windows that was having the problem it was discovered that the issue was being caused by the dialog window. While I was researching the issue a coworker suggest that I turn the DynamicResource into a StaticResource because there was no reason for it to be dynamic.
This change in a dialog window using an resource that was only available within the scope of the dialog window fixed the binding issue described above in the "Main Window". I guess stranger things can happen.
The correct way to debug this is to take the working project and to alternately (modify it to match broken code/confirm it works) until it is either identical to the broken project or it breaks. The point at which it breaks tells you where the problem is. Modifying the broken project is typically a lost cause.
As a secondary point, I'd recommend adding the System.Diagnostics namespace to your XAML. It will make errors show up in the Visual Studio Output window.
xmlns:debug="clr-namespace:System.Diagnostics;assembly=WindowsBase"
As a possibly related point (in that it's not really clear what the problem in the broken project is), you might have a look at this StackOverflow question ("Combobox controling Tabcontrol") that relates to:
WPF,
ComboBoxes,
TabControls, and
binding between them using SelectedIndex.
There isn't yet a solution to this question, but it is a simpler problem.
Lastly, Josh Smith's MSDN code is pretty large. It's hard to figure out what you changed to add your ComboBox without seeing all the code.