I have the following TabControl:
<TabControl ItemsSource="{Binding Tabs"}>
<TabControl.ContentTemplate>
<DataTemplate DataType="{x:Type vm:TabVM}">
<TextBox></TextBox>
<TextBox Text="{Binding SomeProperty}"></TextBox>
</DataTemplate>
</TabControl.ContentTemplate>
</TabControl>
The unexpected behaviour is that first TextBox has Text property shared between all tabitems, while second TextBox effectively bind to ViewModel property.
My need is to make independent the first TextBox too, even without binding.
What can I do ?
** UPDATE **
After several tries I've decided to use the ikriv's TabContent.cs.
The only issue I've found with this is that calling the TabControl.Items.Refresh() (i.e. after removing a tabItem) cause the reset of the internal cache.
An unelegant but effective solution may be this:
public ContentManager(TabControl tabControl, Decorator border)
{
_tabControl = tabControl;
_border = border;
_tabControl.SelectionChanged += (sender, args) => { UpdateSelectedTab(); };
/* CUSTOM */
var view = CollectionViewSource.GetDefaultView(((TabControl)_tabControl).Items);
view.CollectionChanged += View_CollectionChanged;
}
/*
* This fix the internal cache content when calling items->Refresh() method
* */
private void View_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
{
if (e.OldItems != null)
{
/* Retrieve all tabitems cache and store to a temp list */
IList<ContentControl> cachedContents = new List<ContentControl>();
foreach (var item in _tabControl.Items)
{
var tabItem = _tabControl.ItemContainerGenerator.ContainerFromItem(item);
var cachedContent = TabContent.GetInternalCachedContent(tabItem);
cachedContents.Add(cachedContent);
}
/* rebuild the view */
_tabControl.Items.Refresh();
/* Retrieve all cached content and store to the tabitems */
int idx = 0;
foreach (var item in _tabControl.Items)
{
var tabItem = _tabControl.ItemContainerGenerator.ContainerFromItem(item);
TabContent.SetInternalCachedContent(tabItem, cachedContents[idx++]);
}
}
}
You should use data binding since the same ContentTemplate will be applied for all items in your ItemsSource. Only the binding will be refreshed when you switch tabs basically. The TextBox isn't re-created nor reset.
What can I do ?
You could work around this in the view by handling the SelectionChanged event of the TabControl and reset the TextBox control yourself:
private void tabs_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
TabControl tc = sender as TabControl;
ContentPresenter cp = tc.Template.FindName("PART_SelectedContentHost", tc) as ContentPresenter;
if(cp != null && VisualTreeHelper.GetChildrenCount(cp) > 0)
{
ContentPresenter cpp = VisualTreeHelper.GetChild(cp, 0) as ContentPresenter;
if(cpp != null)
{
TextBox textBox = cpp.FindName("txt") as TextBox;
if (textBox != null)
textBox.Text = string.Empty;
}
}
}
<TabControl x:Name="tabs" ItemsSource="{Binding Tabs}" SelectionChanged="tabs_SelectionChanged">
<TabControl.ContentTemplate>
<DataTemplate>
<ContentPresenter>
<ContentPresenter.Content>
<StackPanel>
<TextBox x:Name="txt"></TextBox>
</StackPanel>
</ContentPresenter.Content>
</ContentPresenter>
</DataTemplate>
</TabControl.ContentTemplate>
</TabControl>
If you want to persist the text in the TextBox when you switch tabs you could use the attached behaviour from the following article and set its IsCached property to true: https://www.codeproject.com/articles/460989/wpf-tabcontrol-turning-off-tab-virtualization
<TabControl ItemsSource="{Binding Items}" behaviors:TabContent.IsCached="True">
<!-- Make sure that you don't set the TabControl's ContentTemplate property but the custom one here-->
<behaviors:TabContent.Template>
<DataTemplate>
<StackPanel>
<TextBox />
</StackPanel>
</DataTemplate>
</behaviors:TabContent.Template>
</TabControl>
Yet another approach would be to modify the ControlTemplate of the TabControl to include a ListBox as suggested by 'gekka' in the following thread on the MSDN forums: https://social.msdn.microsoft.com/Forums/en-US/4b71a43a-26f5-4fef-8dc5-55409262298e/using-uielements-on-datatemplate?forum=wpf
Related
I have a lot of bindings in a grid which are collapsed or visible based on the selecteditem in a treeview tree. so based on the object, the correct view pops up and the other collapses.
But I get lots of binding errors of the gui elements in the collapsed views. that is so, because I have a "myselecteditem" in the view model that fits to the view which is opened, but not to the collapsed ones.
is my approach stupid? can i suppress bindings for gui elemnts in a collapsed grid?
Code (shorted for lazy readers):
XAML:
<Grid x:Name="G_G_Content" Grid.Column="1">
<Grid x:Name="G_G_Abrechnung_Control" Visibility="Collapsed">
[...]
</Grid>
<Grid x:Name="G_G_Mitglied_Aktion" Visibility="Collapsed">
[...]
</Grid>
<Grid x:Name="G_G_Mitglied_Aktion_Nachweis" Visibility="Visible">
[...]
</Grid>
</Grid>
<TreeView
x:Name="G_tv_explorer"
Grid.Row="1"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
ItemsSource="{Binding TreeViewItemSource, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
SelectedItemChanged="G_tv_explorer_SelectedItemChanged"
TreeViewItem.Expanded="TreeViewItem_Expanded" />
C#:
private void G_tv_explorer_SelectedItemChanged(object sender, RoutedPropertyChangedEventArgs<object> e)
{
//set active treeviewitem
if (G_tv_explorer.SelectedItem != null)
{
//set treeview && ViMo
ViMo.TreeViewSelectedItem = e.NewValue;
RedrawGui();
ViMo.MySelectedItem = ((TreeViewItem)e.NewValue).Tag;
}
}
public void RedrawGui()
{
//redraw gui, dependant on selectedtreeviewitem
if (ViMo.TreeViewSelectedItem != null)
{
if(ViMo.TreeViewSelectedItem is string)
MessageBox.Show("Dummy");
else
{
//this is a placeholder
if (((TreeViewItem)ViMo.TreeViewSelectedItem).Header.ToString() == "Verwaltung")
{
G_G_Mitglied_Aktion_Nachweis.Visibility = System.Windows.Visibility.Collapsed;
G_G_Abrechnung_Control.Visibility = System.Windows.Visibility.Visible;
}
else if (((TreeViewItem)ViMo.TreeViewSelectedItem).Tag is MVVM.Model.Jahresabschluss)
{
G_G_Mitglied_Aktion_Nachweis.Visibility = System.Windows.Visibility.Collapsed;
G_G_Abrechnung.Visibility = System.Windows.Visibility.Visible;
}
}
}
}
Where RedrawGUI only sets the Grids Visible / collapsed based on the Object Type of the SelectedItem
Here is my code. What it doing. I have one textbox(button) with text Book name, when I click on it I can change text in textbox using binding. But now I add another textbox for author name and I dont know how bind it. If I use same method like for Book name its not works or text from book name is too in book author. All text are changed via popup setting page.
My source here: https://dl.dropbox.com/u/40039421/App1.rar.
Image here: https://dl.dropbox.com/u/40039421/helpp.png
public partial class MainPage : INotifyPropertyChanged
{
private ObservableCollection<Book> _books = new ObservableCollection<Book>();
public ObservableCollection<Book> AllBooks
{
get { return _books; }
set { _books = value; }
}
...
private void InitializeData()
{
var bookName1 = new Book { Id = 1, BookName = "Small red apple",AuthorName = "Daniel"};
...
AllBooks.Add(bookName1);
...
OnPropertyChanged("AllBooks");
}
public event PropertyChangedEventHandler PropertyChanged;
public void OnPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(null, new PropertyChangedEventArgs(propertyName));
}
}
private void btnGreenBook_Click(object sender, RoutedEventArgs e)
{
var button = sender as Button;
if (button != null)
{
Popup popup = new Popup();
popup.VerticalOffset = 30;
PopupSettingPage control = new PopupSettingPage();
popup.Child = control;
popup.IsOpen = true;
control.btnSave.Click += (s, args) =>
{
var id = Convert.ToInt32(button.Tag);
var book = AllBooks.FirstOrDefault(sub => sub.Id == id);
if (book != null)
{
book.BookName = control.BookName;
book.OnPropertyChanged("BookName");
}
popup.IsOpen = false;
};
...
Ohh dear, it was a simple mistake :)
You forgot to add the AuthorName in the Xaml of your PopupSettingsPage.xaml
<TextBox x:Name="tbInputAuthorName" Text="{Binding AuthorName, Mode=TwoWay}" VerticalAlignment="Center"/>
And then in MainPage do this
book.BookName = control.BookName;
book.OnPropertyChanged("BookName");
book.AuthorName = control.AuthorName;
book.OnPropertyChanged("AuthorName");
Additional answer based on your comments:
In order to achieve that, You have to remove the second stackpanel in the listbox. Instead, use the WrapPanel control. Search for how to use WrapPanel for WindowsPhone.
And then you have to find some way to set the backgrounds as red or green. Good luck
OK I use wrap panel but problem is still here. I have two panorama items (I post code from item 1 and item 2 have same code only is showing on second panorama page) and problem is still if I save some value to one of item in panorama item 1 this value is too in panorama item 2 and I dont know how make it. Logic is click on panorama 1 item change values in panoam 1 items, click on panorama 2 items change panorama 2 items values. Its look like panoram item 1 have same ID like panoram item 2. Here is full source : https://dl.dropbox.com/u/40039421/App1SecondEdit.rar
<Grid x:Name="LayoutRoot" Background="Transparent">
<!--Panorama control-->
<phone:Panorama>
<!--Panorama item 1-->
<phone:PanoramaItem Header="Test">
<Grid x:Name="PanelPanoramaItem1"
Grid.Row="1"
Margin="25,0,12,0">
<ListBox ItemsSource="{Binding AllBooks}">
<ListBox.ItemsPanel>
<ItemsPanelTemplate>
<toolkit:WrapPanel Orientation="Vertical"/>
</ItemsPanelTemplate>
</ListBox.ItemsPanel>
<ListBox.ItemTemplate>
<DataTemplate>
<Button Width="140"
Height="140"
toolkit:TiltEffect.IsTiltEnabled="True" Margin="0,0,0,5" Click="Button_Click_1" Tag="{Binding Id}" >
<Button.Template>
<ControlTemplate>
<Grid Background="Chartreuse">
<TextBlock Foreground="Black" Text="{Binding BookName}" />
</Grid>
</ControlTemplate>
</Button.Template>
</Button>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</Grid>
</phone:PanoramaItem>
<!-- END Panorama item 1 -->
This is very strange. The point of the code below is to support an attachedProperty which will notify a container if any of it's children have received focus.
i.e. I have a Grid with a textBox somewhere in it's Content and I want to turn the Grid Blue if one of those controls gets focus.
I have a ListView with an ItemsTemplate. The ItemsTemplate is a DataTemplate containing a few things...but one of them being a ContentControl.
Example:
<ListView>
<ListView.ItemTemplate>
<DataTemplate>
<Grid>
<Border>
<ContentControl Content="{Binding Something}"/>
</Border>
</Grid>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
The Binding on the ContentControl should display a certain type of UserControl.
Upon creation...works just fine. If I recursively iterate template of the listViewItem starting with the Grid element...it will traverse the ContentControl's "Content" as well.
HOWEVER...once I do a .Move() on the ObservableCollection that the ListView ItemsSource is bound to, the ContentControl.Content is empty according to the LogicalTreeHelper.
What gives?
If I inspect the ContentControl, it SHOWS me the content...but LogicalTreeHelper.GetChildren returns and empty Enumerator.
I'm confused...
Can anyone explain why this would be the case?
LogicalTreeHelper iterator method
public static void applyFocusNotificationToChildren(DependencyObject parent)
{
var children = LogicalTreeHelper.GetChildren(parent);
foreach (var child in children)
{
var frameworkElement = child as FrameworkElement;
if (frameworkElement == null)
continue;
Type frameworkType = frameworkElement.GetType();
if (frameworkType == typeof(TextBox) || frameworkType == typeof(ListView) ||
frameworkType == typeof(ListBox) || frameworkType == typeof(ItemsControl) ||
frameworkType == typeof(ComboBox) || frameworkType == typeof(CheckBox))
{
frameworkElement.GotFocus -= frameworkElement_GotFocus;
frameworkElement.GotFocus += frameworkElement_GotFocus;
frameworkElement.LostFocus -= frameworkElement_LostFocus;
frameworkElement.LostFocus += frameworkElement_LostFocus;
// If the child's name is set for search
}
applyFocusNotificationToChildren(child as DependencyObject);
}
}
Aloha,
Here is an suggetion how you could solve your problem:
I am not sure if i spelled right the GotFocus event but its a RoutedEvent and you can use it anywhere in your visual tree.
If one of your items receives a focus your ListView will get notified and inside the handler you can do whatever you want.
How about this:
<ListView GotFocus="OnGotFocus">
<ListView.ItemTemplate>
<DataTemplate>
<Grid>
<Border>
<ContentControl Content="{Binding Something}"/>
</Border>
</Grid>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
This is just some random logic to demonstrate what you can do.
public void OnGotFocus(object sender, RoutedEventArgs e)
{
TreeViewItem item = sender as TreeViewItem;
if(((MyViewModel)item.Content).SomeColor == "Blue")
{
Grid g = VisualTreeHelper.GetChild(item, 0) as Grid;
g.Background = Colors.Blue;
}
}
GotFocus is a RoutedEvent which will bubble up the visual tree if fired. So catch the event somewhere and inspect which was the original source object that fired the event. Or inspect what was the ViewModel propery of the object which fired the event.
I am building a small Windows Phone application which has a databound ListBox as a main control. DataTemplate of that ListBox is a databound ItemsControl element, which shows when a person taps on a ListBox element.
Currently, I am accessing it by traversing the visual tree of the application and referencing it in a list, and than getting the selected item through SelectedIndex property.
Is there a better or more effective way?
This one works currently, but I am afraid if it would stay effective in case of larger lists.
Thanks
Have you tried wiring the SelectionChanged event of the ListBox?
<ListBox ItemsSource="{Binding}" SelectionChanged="ListBox_SelectionChanged">
<ListBox.ItemTemplate>
<DataTemplate>
<!-- ... -->
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
With this in the code behind:
private void ListBox_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
ListBox listBox = sender as ListBox;
// nothing selected? ignore
if (listBox.SelectedIndex != -1)
{
// something is selected
}
// unselect the item so if they press it again, it takes the selection
listBox.SelectedIndex = -1;
}
ListBoxItem item = this.lstItems.ItemContainerGenerator.ContainerFromIndex(yourIndex) as ListBoxItem;
Then you can use the VisualTreeHelper class to get the sub items
var containerBorder = VisualTreeHelper.GetChild(item, 0) as Border;
var contentControl = VisualTreeHelper.GetChild(containerBorder, 0);
var contentPresenter = VisualTreeHelper.GetChild(contentControl, 0);
var stackPanel = VisualTreeHelper.GetChild(contentPresenter, 0) as StackPanel; // Here the UIElement root type of your item template, say a stack panel for example.
var lblLineOne = stackPanel.Children[0] as TextBlock; // Child of stack panel
lblLineOne.Text = "Some Text"; // Updating the text.
Another option is to use services of the GestureServices class available in the WP7 Toolkit.
You'll need to add a GestureListner to the Root Element of your DataTemplate like so:
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel>
<Controls:GestureService.GestureListener>
<Controls:GestureListener Tap="GestureListener_Tap" />
</Controls:GestureService.GestureListener>
<TextBlock x:Name="lblLineOne" Text="{Binding LineOne}" />
<TextBlock Text="{Binding LineTwo}" />
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
And in the GestureListener_Tap event handler, you use this snippet.
private void GestureListener_Tap(object sender, GestureEventArgs e)
{
var itemTemplateRoot = sender as StackPanel;
var lbl1 = itemTemplateRoot.Children[0] as TextBlock;
MessageBox.Show(lbl1.Text);
}
I'm not sure how the GestureListner recognize internally the item being tapped but I guess that it uses the VisualTreeHelper, at least this method is more concise.
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();
}
}