I have a Silverlight application that is using a DataGrid. Inside of that DataGrid I have a DataTemplate that is defined like the following:
<Grid x:Name="myGrid" Tag="{Binding}" Loaded="myGrid_Loaded">
<ItemsControl ItemsSource="{Binding MyItems}" Tag="{Binding}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<StackPanel Orientation="Horizontal" Width="138">
<TextBlock Text="{Binding Type}" />
<TextBox x:Name="myTextBox" TextChanged="myTextBox_TextChanged" />
</StackPanel>
</StackPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Grid>
When a user enters text into the TextBox, I have an event (myTextBox_TextChanged) that must be fired at this point. When that event gets fired, I would like to get the ItemsControl element that is the container for this TextBox. How do I get that ItemsControl from my event handler?
Please note: Because the ItemsControl is in the DataTemplate of DataGrid, I don't believe I can just add an x:Name and reference it from my code-behind. Or is there a way to do that?
Thank you!
Using a combination of ItemsControl.ItemsControlFromItemContainer and VisualTreeHelper.GetParent you should be able to find your ItemsControl
var txt = sender as TextBox;
var panel1 = VisualTreeHelper.GetParent(txt);
var panel2 = VisualTreeHelper.GetParent(panel1);
var contentPresenter = VisualTreeHelper.GetParent(panel2);
var ic = ItemsControl.ItemsControlFromItemContainer(contentPresenter);
You may also want search the web for VisualTreeHelper Recursive functions to make some of this easier.
I like to have this little extension method in a static class somewhere in my app:-
public static IEnumerable<DependencyObject> Ancestors(this DependencyObject root)
{
DependencyObject current = VisualTreeHelper.GetParent(root);
while (current != null)
{
yield return current;
current = VisualTreeHelper.GetParent(current);
}
}
With that you should be able to do something like this:-
ItemsControl control = ((DependencyObject)sender).Ancestors()
.TypeOf<ItemsControl>().FirstOrDefault();
Not sure if this applies but this creates a "toggling button bar" using the same principles.
private void UIClassButton_Click(object sender, RoutedEventArgs e){
Button SenderButton = (Button)sender;
ItemsControl SendersItemControl = ItemsControl.ItemsControlFromItemContainer(VisualTreeHelper.GetParent(SenderButton));
IEnumerable<DependencyObject> DependencyObjectCollection = SendersItemControl.GetContainers();
foreach (ContentPresenter item in DependencyObjectCollection) {
ContentPresenter UIClassPresenter = (ContentPresenter)item;
Button UIClassButton = (Button)UIClassPresenter.GetVisualChildren().First();
if (UIClassButton != SenderButton) {
VisualStateManager.GoToState(UIClassButton, "Normal", true);
}
else {
VisualStateManager.GoToState(UIClassButton, "Pressed", true);
}
}
}
Here's an example of capturing a container that houses your ItemsControl's item:
CheckBox checkbox = sender as CheckBox;
foreach (var item in MembersItemsControl.Items)
{
var container = MembersItemsControl.ItemContainerGenerator.ContainerFromItem(item) as FrameworkElement;
UserInformation user = container.DataContext as UserInformation;
bool isMale = true;
if (user.sex == isMale && checkbox.IsChecked.Value == true)
{
container.Visibility = System.Windows.Visibility.Visible;
}
}
I hope that helps.
Related
I want make TreeView with editable nodes. I googled this good, as I think, article:
http://www.codeproject.com/Articles/31592/Editable-TextBlock-in-WPF-for-In-place-Editing
But I have a problems. My TreeView formed dinamically, not statically as in the arcticle. Like that
<TreeView Name="_packageTreeView" Margin="5" ItemsSource="{Binding PackageExtendedList}">
<TreeView.InputBindings>
<KeyBinding Key="C" Command="{Binding *TestCommand*}" CommandParameter="{Binding}" />
</TreeView.InputBindings>
<i:Interaction.Triggers>
<i:EventTrigger EventName="SelectedItemChanged">
<i:InvokeCommandAction Command="{Binding PackageTreeItemChangeCommand}" CommandParameter="{Binding ElementName=_packageTreeView, Path=SelectedItem}"/>
</i:EventTrigger>
</i:Interaction.Triggers>
<TreeView.ItemContainerStyle>
<Style TargetType="{x:Type TreeViewItem}">
<Setter Property="IsSelected" Value="{Binding Path=IsSelected, Mode=TwoWay}" />
</Style>
</TreeView.ItemContainerStyle>
<TreeView.ItemTemplate>
<HierarchicalDataTemplate DataType="{x:Type MasterBuisnessLogic:RootDocPackage}" ItemsSource="{Binding Path=Childs}">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition>
</ColumnDefinition>
<ColumnDefinition>
</ColumnDefinition>
</Grid.ColumnDefinitions>
<Image Source="/Resources/DocGroup.png"></Image>
<Etb:EditableTextBlock Margin="5,0,0,0" Grid.Column="1" Text="{Binding Path=Name}"></Etb:EditableTextBlock>
</Grid>
</HierarchicalDataTemplate>
</TreeView.ItemTemplate>
</TreeView>
PackageExtendedList - List of DocPackageExtended.
So, first question - how can I get TreeViewItem instance in TestCommand? Not instance DocPackageExtended class! I want to get instance selected TreeViewItem like in the article.
And second question - After I get instance TreeViewItem, how can I get EditableTextBlock from the TreeView item's DataTemplate.
added answer
I already tried it. Cause in MVVM ViewModel cannot has any link to View object like TreeView, I make handler in code-behind, like that
private void TreeViewItemSelected(object sender, RoutedEventArgs e)
{
// Already have TreeViewItem instance without of ItemContainerGenerator help
var tvi = e.OriginalSource as TreeViewItem;
if (tvi == null)
return;
var etb = VisualTreeLib.VisualTreeLib.GetVisualChild<EditableTextBlock>(tvi);
if (etb == null)
return;
// Do what I want
etb.IsEditable = true;
}
Unfortunately, this has no any affect :(
I also tried that approach, but also failed.
in DocPackageExtended type I define property
public bool IsEditable
{
get { return _isEditable; }
set
{
_isEditable = value;
OnPropertyChanged(new PropertyChangedEventArgs("IsEditable"));
}
}
than change in XAML:
<Etb:EditableTextBlock Margin="5,0,0,0" Grid.Column="1" Text="{Binding Path=Name}" *IsEditable="{Binding Path=IsEditable}"*/>
and in ViewModel
private void TestCommandMethod(object obj)
{
var dpe = obj as DocPackageExtended;
if (dpe == null)
return;
dpe.IsEditable = true;
}
Doesn't work too :(
Any ideas?
This might help you.
private void Button_Click(object sender, RoutedEventArgs e)
{
TreeViewItem treeViewItemFound = GetItem(MyTreeview, MyTreeview.SelectedItem);
ContentPresenter header = treeViewItemFound.Template.FindName("PART_Header", treeViewItemFound) as ContentPresenter;
if (header != null)
{
TextBox myTextBox = (TextBox)header.ContentTemplate.FindName("MyTextBox", header);
}
}
public TreeViewItem GetItem(ItemsControl container, object itemToSelect)
{
foreach (object item in container.Items)
{
if (item == itemToSelect)
{
return (TreeViewItem)container.ItemContainerGenerator.ContainerFromItem(item);
}
else
{
ItemsControl itemContainer = (ItemsControl)container.ItemContainerGenerator.ContainerFromItem(item);
if (itemContainer.Items.Count > 0)
{
TreeViewItem treeViewItemFound = GetItem(itemContainer, itemToSelect);
if (treeViewItemFound != null)
return treeViewItemFound;
}
}
}
return null;
}
First question: Since it seems that you can select multiple entries, you need to filter all selected entries in TestCommand's executed method:
IEnumerable<DocPackageExtended> selectedEntries = PackageExtendedList.Where(d => d.IsSelected);
If multiple selection is disabled, you could bind the TreeView's selected item to a property in your VM and access this property in TestCommand's method.
Second question: You get the dataitem's container through var container = YourTreeViewInstance.ItemContainerGenerator.ContainerFromItem(dataInstance);. Now you have to go through this container with the help of the VisualTreeHelper until it finds a control of type EditableTextBlock. But I wouldn't do this in a ViewModel, rather in a helper-class or with the help of attached properties.
EDIT: You're binding the IsEditable property of the instances in the Childs property of your DocPackageExtended class to your EditableTextBox, but in your TestCommandMethod you're manipulating the IsEditableproperty of a DocPackageExtended instance directly. You could do the following:
private void TestCommandMethod(object obj)
{
var dpe = obj as DocPackageExtended;
if (dpe == null)
return;
dpe.IsEditable = true;
foreach (RootDocPackage rdp in dpe.Childs)
{
rdp.IsEditable = true;
}
}
Iam displaying messages in my WPF application
when a new message is added to the messages, i need to highlight it.so i want to dynamically get the text added to TextBlock
i have the xaml like this
<ItemsControl Name="DialogItemsControl" ItemsSource="{Binding Messages, Mode=OneWay}" Background="Transparent"
BorderBrush="Transparent" TargetUpdated="DialogItemsControl_TargetUpdated">
<ItemsControl.ItemTemplate><!-- For ever message -->
<DataTemplate>
<Grid Margin="0,0,0,20">
<ItemsControl Name="SubDialogItemsControl"
Foreground="{DynamicResource ButtonTextBrush}"
ItemsSource="{Binding Lines,NotifyOnTargetUpdated=True}"
Margin="0,0,0,12"
Grid.Column="0">
<ItemsControl.ItemTemplate><!-- For every line -->
<DataTemplate>
<TextBlock Name="DialogMessageText"
Text="{Binding NotifyOnTargetUpdated=True}"
VerticalAlignment="Top"
Margin="0,2,0,2"
TextTrimming="WordEllipsis"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Grid>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
and the code in the codebehind class is like this:
private void DialogItemsControl_TargetUpdated(object sender, System.Windows.Data.DataTransferEventArgs e)
{
ItemsControl itemControl = sender as ItemsControl;
ContentPresenter dp = itemControl.ItemContainerGenerator.ContainerFromItem(itemControl.Items.CurrentItem) as ContentPresenter;
// Finding textBlock from the DataTemplate that is set on that ContentPresenter
DataTemplate myDataTemplate = dp.ContentTemplate;
ItemsControl itc = (ItemsControl)myDataTemplate.FindName("SubDialogItemsControl", dp);
if (itc != null && itc.ItemContainerGenerator.Status == System.Windows.Controls.Primitives.GeneratorStatus.ContainersGenerated)
{
ContentPresenter cp = itc.ItemContainerGenerator.ContainerFromIndex(0) as ContentPresenter;
DataTemplate dt = cp.ContentTemplate;
TextBlock tb = dt.LoadContent() as TextBlock;
tb.TargetUpdated += new EventHandler<System.Windows.Data.DataTransferEventArgs>(myTextBlock_TargetUpdated);
}
}
void myTextBlock_TargetUpdated(object sender, System.Windows.Data.DataTransferEventArgs e)
{
TextBlock tb = sender as TextBlock;
//When i access the text property of tb, its showing null, how to get the text
}
When i access the text property of textblock in the target updated event of textblock, its showing null, how to read the text.
Thanks in advance
You tackle the problem from the wrong angle (and probably add a memory leak in the process since I don't see you unsubscribing to the event).
You need to create a Custom TextBlock, overriding the metadata of the Text property so that it changes the Background for a few seconds when the text string changes (through PropertyChangedCallback).
And then use that custom TextBlock in the DataTemplate of your ItemsControl.
EDIT - I thought other people could need this feature so here is a working example:
public class CustomTextBlock : TextBlock
{
static CustomTextBlock()
{
TextProperty.OverrideMetadata(typeof(CustomTextBlock), new FrameworkPropertyMetadata(null,
new PropertyChangedCallback(
(dpo, dpce) =>
{
//Flash the background to yellow for 2 seconds
var myTxtblk = dpo as CustomTextBlock;
if (myTxtblk != null)
{
myTxtblk.Background = Brushes.Yellow;
Task.Factory.StartNew(
() =>
{
Thread.Sleep(2000);
Application.Current.Dispatcher.Invoke(
new Action(() =>
{
myTxtblk.Background = Brushes.Transparent;
}));
});
}
})));
}
}
Then you need to declare the right xmlns namespace in your XAML view, and you use it like a regular TextBlock:
<local:CustomTextBlock Text="{Binding MyDynamicText}"/>
It will flash yellow when MyDynamicText is modified (provided it raises PropertyChanged).
When I make selection in ComboBox, and then type some text in TextBox, I want to have visible AutoSuggestion list of ID or FirstName or LastName (based on ComboBox Selection) that contains typed string in TextBox. Like this, now it works only for FirstName.
I have problem to somehow set dynamically binding for TextBlock.
Please Help.
Thanks in advance! Marina
I have ComboBox:
<ComboBox Height="23" Name="cbAttrib" Width="120" Margin="0,8,0,0">
<ComboBoxItem>ID</ComboBoxItem>
<ComboBoxItem>FirstName</ComboBoxItem>
<ComboBoxItem>LastName</ComboBoxItem>
</ComboBox>
I have TextBox:
<TextBox Name="txtSearch" TextChanged="txtAutoSuggestName_TextChanged"/>
And this ListBox:
<ListBox Name="listBoxSuggestion" Visibility="Hidden" SelectionChanged="ListBox_SelectionChanged">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBlock DataContext="{Binding FirstName}"/>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
and in code I have this methods:
private void txtAutoSuggestName_TextChanged(object sender, TextChangedEventArgs e)
{
listBoxSuggestion.Items.Clear();
if (txtSearch.Text != "")
{
ComboBoxItem cb = (ComboBoxItem)cbAttrib.SelectedItem;
Collection<Person> namelist = proxy.PersonSearch(txtSearch.Text, cb.Content.ToString());
if (namelist.Count > 0)
{
listBoxSuggestion.Visibility = Visibility.Visible;
foreach (var obj in namelist)
{
listBoxSuggestion.Items.Add(obj);
}
}
}
else
{
listBoxSuggestion.Visibility = Visibility.Hidden;
}
}
private void ListBox_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
if (e.AddedItems.Count > 0)
{
txtSearch.Text = (e.AddedItems[0] as Person).FirstName.ToString();
listBoxSuggestion.Visibility = System.Windows.Visibility.Hidden;
}
}
You are not binding the Text so nothing will display
You just bind the DataContext, which does nothing if there are no additional bindings which will be relative to it. Just swap that (or add Text="{Binding}" which will bind to the DataContext which is the FirstName) and if your logic is correct it should work.
(Instead of clearing and adding to Items you should just set the ItemsSource instead. listBoxSuggestion.ItemsSource = namelist;)
Edit: To make the binding work for different suggestions change the binding path to Value and make the ItemsSource a collection of some simple objects with a Value property (e.g. use LINQ and anonymous objects).
i made a listbox that generates dynamic controls such as dropdowns & datepicker. i wanted to retrieve the data within the rows. Normally, in windows forms we commonly index the ...Items[i].FindControl("ControlID") method. How do you do about in XAML?
I need to retrieve the changes upon clicking a button.
btw, here's a simple view of my xaml:
<ListBox>
<stackpanel>
<TextBlock />
<stackpanel>
<grid>
<combobox />
<combobox/>
<datepicker />
</grid>
</stackpanel>
</stackpanel>
</ListBox>
Thank you so much!
private void ListBox_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
FrameworkElement selectedItem = (sender as ListBox).SelectedItem as FrameworkElement;
List<FrameworkElement> children = new List<FrameworkElement>();
children = GetChildren(selectedItem, ref children);
}
private List<FrameworkElement> GetChildren(FrameworkElement element, ref List<FrameworkElement> list)
{
int count = VisualTreeHelper.GetChildrenCount(element);
for (int i = 0; i < count; i++)
{
FrameworkElement child = VisualTreeHelper.GetChild(element, i) as FrameworkElement;
if(child != null)
{
list.Add(child);
GetChildren(child, ref list);
}
}
return list;
}
This returns all the FrameworkElements (including paths, borders etc). You can easily extend it and call the GetChildren method recursively only if the child is of certain type (ComboBox, StackPanel etc)
I have a helper class with the following two methods to assist with this sort of task.
XAML:
<ListBox Height="236" HorizontalAlignment="Left" Margin="31,23,0,0"
Name="listBox1" VerticalAlignment="Top" Width="245">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal" Name="sp">
<TextBlock Name="id">id</TextBlock>
<TextBox Name="test" Text="{Binding Key}"></TextBox>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
Using with a List box, you could pass in the selected item:
var v1 =(ListBoxItem) listBox1.ItemContainerGenerator.ContainerFromIndex(
listBox1.SelectedIndex);
TextBox tb = GetChildByName<TextBox>(v1, "test");
tb.Text = "changed";
and you would get the correct textbox for that selected list box item. You can then use that reference to change properties on it.
public T GetChildByName<T>(DependencyObject parent, string name)
where T : class
{
T obj = RecGetChildByName<T>(parent, name) as T;
if (obj == null) throw new Exception("could find control "
+ "of name as child");
return obj;
}
private DependencyObject RecGetChildByName<T>(DependencyObject parent,
string name)
{
for (int i = 0; i < VisualTreeHelper.GetChildrenCount(parent); i++)
{
var child = VisualTreeHelper.GetChild(parent, i);
Control childControl = child as Control;
if (childControl != null)
{
if (childControl.Name == name) return child;
}
if (VisualTreeHelper.GetChildrenCount(child) > 0)
return RecGetChildByName<T>(child, name);
}
return null;
}
The most straightforward way would be to set a two-way binding on of your controls to objects and then the objects will tell you what the values were set to.
Also you can go through your tree by going through the Content properties of the objects until you get to the leaf objects.
Alternatively, you can use the Selected item and call the VisualTreeHelper's GetChild Method until you're at the leaf objects.
I have a listbox with a bunch of contols in each list item.
<ListBox x:Name="projectList" IsSynchronizedWithCurrentItem="True">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel>
<TextBlock Text="{Binding Name}" />
<ListBox x:Name="taskList" ItemsSource="{Binding Tasks}">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel>
<TextBlock Text="{Binding Name}" />
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
<TextBox x:Name="textBoxTask" />
<Button
x:Name="ButtonAddNewTask"
Content="Test"
CommandParameter="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=DataContext}"
Click="ButtonAddNewTask_Click"
/>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
When I click on the button in the listbox i want to add a new item to the listbox within the listbox. I've come this far. So my question is how do I get hold of the textbox and how do I update the listbox?
Here is my click event
private void ButtonAddNewTask_Click(object sender, RoutedEventArgs e)
{
Button button = (Button)sender;
Project proj = button.DataContext as Project;
if(proj.Tasks == null)
proj.Tasks = new List<Task>();
proj.Tasks.Add(new Task("Added Task"));
}
Thanx
The easiest solution would likely be to have one object represent each item in the outer ListBox. It would then have properties that would represent each control in the item - the text in the TextBox, and the items in the ListBox (a list of Tasks, I think, based on your Click handler).
In your Click handler, you can get the Button's DataContext (which should be an item in the collection of the outer list), and add a new Task to that object's list of tasks. Since the inner ListBox is bound to that list, it should be updated with the new item (assuming that it sends events when items are added, such as with ObservableCollection).
Update: Based on your comments, the following should work.
Your Project class should have two properties:
class Project
{
public string Name { get; set; }
private ObservableCollection<Task> tasks =
new ObservableCollection<Task>();
public IList<Task> Tasks
{
get { return this.tasks; }
}
}
The Task class just has one property - the name of the task.
The ProjectView class is a wrapper around the Project class (I got this idea from #timothymcgrath's answer). It keeps track of the name of a new task, and the current Project:
class ProjectView : INotifyPropertyChanged
{
public Project Project { get; set; }
private string newTaskName = string.Empty;
public string NewTaskName
{
get { return this.newTaskName; }
set
{
this.newTaskName = value;
this.OnPropertyChanged("NewTaskName");
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string propName)
{
PropertyChangedEventHandler eh = this.PropertyChanged;
if(null != eh)
{
eh(this, new PropertyChangedEventArgs(propName));
}
}
}
You'll need a new class that will be used as the DataContext. Something like this:
class Model
{
private ObservableCollection<ProjectView> projects =
new ObservableCollection<ProjectView>();
public IList<ProjectView> Projects
{
get { return this.projects; }
}
}
In the code behind, set the DataContext of the object to an instance of the above class:
public class Window1
{
public Window1()
{
this.InitializeComponent();
this.DataContext = this.model;
}
private Model model = new Model();
}
In the XAML, the bindings should be modified to bind to the above properties:
<ListBox x:Name="projectList" IsSynchronizedWithCurrentItem="True"
ItemsSource="{Binding Path=Projects}">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel>
<TextBlock Text="{Binding Project.Name}" />
<ListBox x:Name="taskList"
ItemsSource="{Binding Project.Tasks}"
DisplayMemberPath="Name" />
<TextBox x:Name="textBoxTask"
Text="{Binding Path=NewTaskName, UpdateSourceTrigger=PropertyChanged}"/>
<Button x:Name="ButtonAddNewTask" Content="Test"
Click="ButtonAddNewTask_Click" />
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
Finally, in the click handler for the button, create the task. The DataContext of the Button will be the ProjectView for that item.
private void ButtonAddNewTask_Click(object sender, RoutedEventArgs e)
{
Button btn = (Button)sender;
ProjectView curProject = btn.DataContext as Project;
if(null != curProject)
{
curProject.Project.Tasks.Add(new Task()
{
Name = curProject.NewTaskName
});
}
}
Since all of the controls get their values via binding, you don't need to access the control itself to get the data - just use the data structures that are supplying the controls already.
It would probably be better to move the code that creates the Task into another class (possibly Project), but I just left it in the event handler for ease of typing on my part.
Update 2: Modified the above code to move the NewTaskName property into a separate class that wraps an instance of Project for use with the UI. Does this work better for you?
I'm assuming your Project ListBox is populated with an Collection of Project objects. I would add an AddNewTask ICommand to the Project class and expose it through a property. Then bind the Add New Task button to the new AddNewTask ICommand. For the CommandParameter, put the TaskName in and it will be passed into the command.
Try reading up on some MVVM (Model View ViewModel) for some examples of how this works. It is very clean and works great.
This solution worked for the task at hand so to speak.
private void ButtonAddNewTask_Click(object sender, RoutedEventArgs e)
{
Button button = (Button)sender;
DependencyObject obj = LogicalTreeHelper.GetParent(button);
StackPanel item = obj as StackPanel;
TextBox textBox = item.FindName("textBoxTask") as TextBox;
ListBox listBox = item.FindName("taskList") as ListBox;
Project proj = button.DataContext as Project;
if(proj.Tasks == null)
proj.Tasks = new List<Task>();
listBox.ItemsSource = proj.Tasks;
listBox.Items.Refresh();
}