I have a list box displaying the names of help topics which can be added to and the names of the topics changed. Originally it was just displaying strings, but to get the inline editing working I changed it to use a custom type consisting of a string and an InEdit property so the UI can determine whether to display the TextBlock or TextBox:
XAML:
<ListBox ItemsSource="{Binding HelpTopics, Mode=TwoWay}"
SelectedValuePath="Description"
SelectedValue="{Binding SelectedPageId, Mode=TwoWay}"
SelectionChanged="ListBox_SelectionChanged">
<ListBox.ItemTemplate>
<DataTemplate>
<Grid>
<TextBlock Text="{Binding Description, Mode=TwoWay}"
VerticalAlignment="Center"
MouseLeftButtonUp="TopicTextBlock_MouseLeftButtonUp"
Visibility="{Binding InEdit, Converter={StaticResource boolToVisibilityConverter}, ConverterParameter=contra}"/>
<TextBox Text="{Binding Description, Mode=TwoWay}"
Visibility="{Binding InEdit, Converter={StaticResource boolToVisibilityConverter}, ConverterParameter=pro}"
LostFocus="EditTopicTextBox_LostFocus"
HorizontalAlignment="Stretch" VerticalAlignment="Center"/>
</Grid>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
<Button Margin="5" Content="Add Topic" Command="{Binding AddTopicCommand}"/>
HelpTopics is an ObservableCollection<EditableHelpTopic>.
SelectedPageId is a string.
boolToVisibilityConverter is a converter that does what it says.
What works:
Adding a topic creates a new item and adds it to the list and put the item in to edit mode.
Double clicking on an existing item puts that item into edit mode sets the focus to the TextBox and selects all the text so it can be overwritten.
When the TextBox loses focus the edit is saved and the display returns to the TextBlock.
What doesn't work:
When a new topic is added the TextBox should have focus and the text selected so the user can enter a new name.
So my question is is there a point in the code or an event where I know that the TextBox has been created and is visible so I can set focus and select its contents. I've tried hooking into the SelectionChanged event but when that fires the TextBox hasn't yet been displayed. I also added an event to the OnAddTopicExecute method in the view model which I handled in the view, but again that fired before the TextBox was visible.
Below is the code that supports the above XAML. I've tried to cut it down, but there still seems to be a lot of it, so you can skip this if you're not interested ;)
Code behind:
private DateTime lastClickTime = DateTime.MinValue;
private Point lastClickPosition;
private void TopicTextBlock_MouseLeftButtonUp(object sender, System.Windows.Input.MouseButtonEventArgs e)
{
UIElement element = sender as UIElement;
if ((DateTime.Now - this.lastClickTime).TotalMilliseconds > 300)
{
this.lastClickPosition = e.GetPosition(element);
this.lastClickTime = DateTime.Now;
}
else
{
Point position = e.GetPosition(element);
if (Math.Abs(this.lastClickPosition.X - position.X) < 4 && Math.Abs(this.lastClickPosition.Y - position.Y) < 4)
{
var textBlock = sender as TextBlock;
var editableHelpTopic = textBlock.DataContext as EditableHelpTopic;
editableHelpTopic.InEdit = true;
var parent = textBlock.Parent as Grid;
TextBox textBox = parent.Children.First(c => c.GetType() == typeof(TextBox)) as TextBox;
textBox.Focus();
textBox.SelectAll();
}
}
}
private void EditTopicTextBox_LostFocus(object sender, RoutedEventArgs e)
{
var textBox = sender as TextBox;
var editableHelpTopic = textBox.DataContext as EditableHelpTopic;
editableHelpTopic.InEdit = false;
if (!textBox.Text.Equals(editableHelpTopic.Description))
{
this.editViewModel.RenameTopic(textBox.Text);
}
}
View Model:
public EditViewModel()
{
...
this.AddTopicCommand = new DelegateCommand(this.OnAddTopicExecute, this.OnAddTopicCanExecute);
...
}
where DelegateCommand is an implemetation of ICommand.
private void OnAddTopicExecute(object parameter)
{
var newTopic = new EditableHelpTopic
{
Description = "NewTopic",
InEdit = true
};
this.HelpTopics.Add(newTopic);
this.SelectedPageId = newTopic.Description;
}
Definitions:
public class EditableHelpTopic : INotifyPropertyChanged
{
public bool InEdit { ... }
public string Description { ... }
}
It turned out to be simpler than I thought.
I just needed to add a Loaded event handler to the TextBox:
private void EditTopicTextBox_Loaded(object sender, RoutedEventArgs e)
{
var textBox = sender as TextBox;
var editableHelpTopic = textBox.DataContext as EditableHelpTopic;
if (editableHelpTopic.InEdit)
{
textBox.Focus();
textBox.SelectAll();
}
}
Related
I would like to change the textbox default textcolor to the original default color after item is added to a list.
XAML
<TextBox Name="AddLocationTextBox" Text="{Binding Path=AddLocationName, UpdateSourceTrigger=PropertyChanged}"
LostFocus="AddLocationTextBox_LostFocus" GotFocus="AddLocationTextBox_GotFocus" HorizontalAlignment="Left" Height="23" Margin="10,37,0,0" TextWrapping="Wrap" VerticalAlignment="Top" Width="285">
<TextBox.InputBindings>
<KeyBinding Key="Enter" Command="{Binding AddLocationCommand}" />
</TextBox.InputBindings>
</TextBox>
Code behind in View
public LocationManagerView()
{
InitializeComponent();
AddLocationTextBox.Foreground = Brushes.Gray;
}
private void AddLocationTextBox_GotFocus(object sender, RoutedEventArgs e)
{
AddLocationTextBox.Text = string.Empty;
AddLocationTextBox.Foreground = Brushes.Black;
}
private void AddLocationTextBox_LostFocus(object sender, RoutedEventArgs e)
{
AddLocationTextBox.Foreground = Brushes.Gray;
}
ViewModel
public RelayCommand AddLocationCommand { get; private set; }
private void AddLocation()
{
if ( AddLocationName != null)
{
Locations.Add(new Location()
{
Name = AddLocationName,
});
AddLocationName = "Enter New Location";
Keyboard.ClearFocus();
////change textcolor to gray////
}
else
{
return;
}
}
It's after AddLocationName is set again to "Enter New Location" I want to change the textcolor back to gray.
Looks like the Keyboard.ClearFocus() doesn't call the LostFocus method in the View.
Any Idea how I make this work?
If you just want to change the foreground of your TextBox to gray, instead of tying to force AddLocationTextBox to lose it's focus, you should change the foreground manually (i.e. use AddLocationTextBox.Foreground = Brushes.Gray instead of Keyboard.ClearFocus()).
If you're looking for a way to force currently focused element to lose focus, then you can use below code instead of Keyboard.ClearFocus() :
UIElement elementWithFocus = Keyboard.FocusedElement as UIElement;
elementWithFocus.MoveFocus(new TraversalRequest(FocusNavigationDirection.Next));
My item class has string name and bool picked. I use a class with a static ObservableCollection with static methods to maintain the list. This is all working.
I cannot access the checkbox within the listbox item. I've tried multiple ideas I've found on Stack Overflow and elsewhere. I have tried so many things, it would be too long to mention them all.
This is the latest attempt.
It works when I leave the checkbox out of the code.
I understand I'm failing to access the checkbox, it's not being recognized as part of the list item.
But I just don't know how to fix it.
<ListBox x:Name="ListBox1" ItemsSource="{Binding}"
DoubleTapped="ListBox1_DoubleTapped" SelectionMode="Multiple">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<CheckBox x:Name="checkBox" Checked="CheckBox_Checked"
IsChecked="{Binding Picked}"/>
<TextBlock Text="{Binding Name}"/>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
public sealed partial class MainPage : Page
{
Binding myBinding = new Binding();
public MainPage()
{
//....stuff
ListBox1.DataContext = MyList.list;
//... etc
// This works!!
protected override void OnNavigatedTo(NavigationEventArgs e)
{
// Update listbox after item has been added and
// user has returned to main page.
ListBox1.UpdateLayout();
}
// This works if I leave the checkbox out of it!!
private void ListBox1_DoubleTapped(object sender, Windows.UI.Xaml.Input.DoubleTappedRoutedEventArgs e)
{
CheckBox checkBox = (CheckBox)this.FindName("checkBox");
// Find the index;
int i = ListBox1.SelectedIndex;
// Some stuff..
// This is what is bound to the checkbox in the xaml!!
item.Picked = true;
try
{
//Manually trying to change the checked of the checkbox!!
// Yes increasingly desperate!!
checkBox.IsChecked = true;
}// Necessary as the checkbox is always throwing this.
catch (NullReferenceException e) { }
}
// Alter Picked value to true on checkbox selection.
private void CheckBox_Checked(object sender, RoutedEventArgs e)
{
// Find the index;
//THE INDEX IS ALWAYS 1!!!
int i = ListBox1.SelectedIndex;
try
{
//Trying again to manually manipulate, even though
//the data is supposed to be bound.
item.Picked = true;
}
catch (NullReferenceException ex){}
}
I've tried to only include essential information.
I've left out margins, colors etc and basic declarations.. etc to try and reduce the code.
access a named control inside a XAML DataTemplate
I thought I'd provide an answer to elaborate on the accepted answer with the link that solved this for me.
Basically this link provides a good way to loop through the xaml hierarchy using a visual tree, to find controls within the ListBox. So my xaml looks like this:
<ListBox x:Name="ListBox1" ...>
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel ...>
<CheckBox x:Name="checkBox" ... />
<TextBlock ... />
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
// To select list item and change Picked value to true.
private void ListBox1_DoubleTapped(object sender, Windows.UI.Xaml.Input.DoubleTappedRoutedEventArgs e)
{
// Find the index;
int i = ListBox1.SelectedIndex;
CheckBox checkBox = getCheckBox(ListBox1);
try
{
// Change Picked bool value.
item.Picked = true;
// Check CheckBox to show item selected.
checkBox.IsChecked = true;
}
catch (NullReferenceException exc) { }
}
// Taken and modified from Jerry Nixon. http://blog.jerrynixon.com/2012/09/how-to-access-named-control-inside-xaml.html
// Find the checkBox for that ListBox item.
CheckBox getCheckBox(ListBox ListBox1)
{
var _ListBoxItem = ListBox1.SelectedItem;
var _Container = ListBox1.ItemContainerGenerator.ContainerFromItem(_ListBoxItem);
var _Children = AllChildren(_Container);
var _Name = "checkBox";
var _Control = (CheckBox)_Children.First(c => c.Name == _Name);
return _Control;
}
// Taken and modified from Jerry Nixon. http://blog.jerrynixon.com/2012/09/how-to-access-named-control-inside-xaml.html
// Get any child controls from ListItem Container.
// Using a visual tree to access elements on the page
// within the xaml heirarchy of nested elements/tags.
public List<Control> AllChildren(DependencyObject parent)
{
var _List = new List<Control> { };
for (int i = 0; i < VisualTreeHelper.GetChildrenCount(parent); i++)
{
var _Child = VisualTreeHelper.GetChild(parent, i);
if (_Child is Control)
_List.Add(_Child as Control);
_List.AddRange(AllChildren(_Child));
}
return _List;
}
Basically this can be used for other events and controls. Useful code to have.
I have a ListPicker in an application page, but the SelectionChanged event gets called multiple times as the page loads. To avoid this, I have been following a previous question I asked here ListPicker SelectionChanged Event Called Multiple Times During Navigation in which the suggestion was instead of making ThemeListPicker_SelectionChanged make a parent stackpanel inside the datatemplate..', create a tap event in the StackPanel called stk_Tap, and 'use this tap stk_Tap to do your action as, this event would also get called every time the selection changed gets called but, it wont exhibit the buggy behavior like that of selection changed event'
Now I have adjusted my solution accordingly, but I do not know how to determine which item of the ListPicker is being selected or is currently selected. Also I removed the ListPicker SelectionChanged event in the ListPicker because I thought the StackPanel could get the item, but I am not sure if this is correct or how to do this?
XAML
<phone:PhoneApplicationPage.Resources>
<DataTemplate x:Name="PickerItemTemplate">
<TextBlock Text="{Binding Name}"/>
</DataTemplate>
</phone:PhoneApplicationPage.Resources>
<toolkit:ListPicker x:Name="ThemeListPicker" Header="Theme"
ItemTemplate="{StaticResource PickerItemTemplate}"
SelectionChanged="ThemeListPicker_SelectionChanged"/>
XAML.CS
private void ThemeListPicker_SelectionChanged(object sender,
SelectionChangedEventArgs e)
{
if(ThemeListPicker.SelectedIndex != -1)
{
var theme = (sender as ListPicker).SelectedItem;
if (index == 0)
{
Settings.LightTheme.Value = true;
MessageBox.Show("light");
}
else
{
Settings.LightTheme.Value = false;
MessageBox.Show("dark");
}
}
}
*EDIT: How I updated my solution
<phone:PhoneApplicationPage.Resources>
<DataTemplate x:Name="PickerItemTemplate">
<StackPanel tap="stk_Tap">
<TextBlock Text="{Binding Name}"/>
</StackPanel>
</DataTemplate>
</phone:PhoneApplicationPage.Resources>
<toolkit:ListPicker x:Name="ThemeListPicker" Header="Theme"
ItemTemplate="{StaticResource PickerItemTemplate}"
/>
So, even when I left the ListPicker SelectionChanged event in the code behind after making the modifications, I did not see the event being called twice upon the page loading/navigating to, but I am not sure how to get the currently selected item now?
EDIT2**
protected override void OnNavigatedTo(NavigationEventArgs e)
{
base.OnNavigatedTo(e);
themeList = new List<TestApp.Common.Theme>();
themeList.Add(new TestApp.Common.Theme() { Name = "Darker", name = "dark" });
themeList.Add(new TestApp.Common.Theme() { Name = "Lighter", name = "light" });
ThemeListPicker.ItemsSource = themeList;
}
private void stk_Tap(object sender, System.Windows.Input.GestureEventArgs e)
{
if (ThemeListPicker.SelectedIndex != -1)
{
//Need to get the current ThemeListPicker's 'name'
var selectedItem1 = (sender as StackPanel).DataContext as ListPicker;
//use selectedItem1
}
}
No need to extra tap event for such kind of work.
private void ThemeListPicker_SelectionChanged(object sender,
SelectionChangedEventArgs e)
{
if(ThemeListPicker.SelectedIndex==-1)
return;
var theme = (sender as ListPicker).SelectedItem;
if (index == 0)
{
Settings.LightTheme.Value = true;
MessageBox.Show("light");
}
else
{
Settings.LightTheme.Value = false;
MessageBox.Show("dark");
}
ThemeListPicker.SelectedIndex=-1
}
ListPicker SelectionChanged Event Called Multiple Times During Navigation
for above problem if i guess right you set listpicker's itemssource on OnNavigatedTo event. so modify you r onNavigatedTo method with
protected override void OnNavigatedTo(NavigationEventArgs e)
{
base.OnNavigatedTo(e);
if (e.NavigationMode != NavigationMode.Back)
{
// Your code goes here
}
}
//Stack panel tap event
private void stack_Tap(object sender, System.Windows.Input.GestureEventArgs e)
{
var selectedIrem = (Cast as your type)(sender as StackPanel).DataContext;
}
I have three texboxes and one slider which changes their Text properties. What i have to do is to bind slider's value property with Text textbox property but in a specific way. When one of textboxes are activated(gotfocused) i need slider to change its Text property. And only that one. I have binded it so far but when i move the slider all textboxes are updated.
Any ideas?
I was reading about converters, but i don't see how to implement it within my program.
http://forums.create.msdn.com/forums/t/95548.aspx here you have got code of my slider and textblock.
What about simply changing the active binding when a textbox receives focus:
Code Behind:
private Binding _activeBinding;
private TextBox _activeTextbox;
private TextBox ActiveTextBox
{
get { return _activeTextbox; }
set
{
// Check if a binding exists, initialize if one does not
if (_activeBinding == null)
{
_activeBinding = new Binding("Value");
_activeBinding.Source = this.sld;
}
if (_activeTextbox != null)
{
// Clear the binding
_activeTextbox.ClearValue(TextBox.TextProperty);
}
_activeTextbox = value;
if (_activeTextbox != null)
{
// Set the new binding
_activeTextbox.SetBinding(TextBox.TextProperty, _activeBinding);
}
}
}
private void TextBox_GotFocus(object sender, RoutedEventArgs e)
{
this.ActiveTextBox = sender as TextBox;
}
XAML:
<Grid>
<StackPanel>
<TextBox GotFocus="TextBox_GotFocus">1</TextBox>
<TextBox GotFocus="TextBox_GotFocus">2</TextBox>
<TextBox GotFocus="TextBox_GotFocus">3</TextBox>
<Slider x:Name="sld"></Slider>
</StackPanel>
</Grid>
I have created a registration form in silverlight 4, where i have a large number of text-boxes, in front of each text box i have placed a text-block as a required field validator, when any of the textbox left empty while loosing focus, the textblock placed in front of it must become red.
textboxes named textbox1, textbox2 ... and so as the textblocks
the problem is, i do not want code the specific method for each specific textbox, all i want to do is to complete such in just two three methods
here i did some coding which doesn't seems to be correct
private void textBox_LostFocus(object sender, RoutedEventArgs e)
{
var textBox = (TextBox) sender;
if (textbox.Text == "")
{
var textblock = "textblock" + textBox.Name.Remove(0,7);
TextblockColorChange(textblock);
}
}
private void TextblockColorChange(object sender)
{
var textblock = (TextBlock) sender;
textblock.Foreground= new SolidColorBrush(Colors.Red);
}
please suggest some better way to do so..
I'd create a UserControl that contains the TextBlock and the TextBox and use this UserControl everywhere you currently have the TextBlock and TextBox combination. Then this Usercontrol would have the LostFocus logic inside it and update the TextBlock appropriately. This prevents the need to figure out the right name of the control to update.
you need something like this,
XAML part:
<Grid x:Name="LayoutRoot" Background="White">
<StackPanel Orientation="Horizontal" Height="25">
<TextBox Width="150" LostFocus="TextBox_LostFocus"/>
<TextBlock Text="*" Foreground="#FF0000" VerticalAlignment="Center" Margin="10,0,0,0" Visibility="Collapsed"/>
</StackPanel>
</Grid>
C# Part:
private void TextBox_LostFocus(object sender, RoutedEventArgs e)
{
var textbox = sender as TextBox;
if(textbox == null) return;
var stackPanel = textbox.Parent as StackPanel;
if(stackPanel == null) return;
var textBlock = stackPanel.Children.Where(a => a is TextBlock).FirstOrDefault();
if (textBlock == null) return;
if (string.IsNullOrEmpty(textbox.Text)) textBlock.Visibility = Visibility.Visible;
else textBlock.Visibility = Visibility.Collapsed;
}
Whilst I actually prefer Bills approach (although I'd be inclined to use a Templated Control) here is another alternative which is quite fun. In your xaml use this sort of markup:-
<TextBlock Text="Enter Value 1" Foreground="{Binding Tag, ElementName=textBox1, TargetNullValue=Black}" />
<TextBox x:Name="textBox1" LostFocus="txt_LostFocus" />
Your common txt_LostFocus can look like this:-
private void txt_LostFocus(object sender, RoutedEventArgs e)
{
TextBox txt = ((TextBox)sender);
if (String.IsNullOrEmpty(txt.Text))
{
txt.Tag = new SolidColorBrush(Colors.Red);
}
else
{
txt.Tag = null;
}
}
var textblock = "textblock" + textBox.Name.Remove(0,7);
TextblockColorChange(textblock);
This code above will just send a string to TextblockColorChange()
You don't show any other code, but I'm guessing you want to do a FindControl or FindControl like search on that string before passing the result to your code.