I want to get the Grid control that is wrapped in a border (no Name is assigned), the structure look like this:
<Grid x:Name="main">
<uc:myUc/>
<Border>
<!--other elements-->
</Border>
<Border>
<Grid x:Name="myGrid">
<!--I want to get all controls here-->
</Grid>
</Border>
</Grid>
myUc is a user control that has a button that when clicked, I want to get all the controls inside the grid myGrid:
this is the code I 'm using, it seems I should give a Name to the container Border to be able to get its children, but this is a huge change in my application.
var parent = VisualTreeHelper.GetParent(this) as UIElement;
var grid = (parent as Grid);
var chldrn = grid.Children;
foreach (var item in chldrn)
{
var child = item;
}
//I stopped here!
Is there a way to find all descendants of an element to determie which control I take?
You can access any kind of child in containers by their type
XAML Example:
<Grid x:Name="myGrid">
<Button Content="Button1"></Button>
<Button Content="Button2"></Button>
<CheckBox Content="CheckBox1"></CheckBox>
</Grid>
C# Example
private void Button_Click(object sender, RoutedEventArgs e)
{
foreach(var Child in myGrid.Children.OfType<Button>()) // this will only get buttons in grid's children
{
MessageBox.Show(Child.Content);
}
//another way:
foreach(var Child in myGrid.Children)
{
if (Child is CheckBox)
{
var checkBox = (CheckBox)Child;
checkBox.IsChecked = true;
}
}
}
Related
I have created my custom adorner to cover my main window with a gray canvas alongwith a textblock at center to show some status text while i was working on other window.
What i am currently doing is fetching the required adornerElement(ie Canvas with a textblock) from my resources and passing it to an adorner in my view constructor like this -
ResourceDictionary reportResourceDictionary = App.LoadComponent(new Uri("Resources/ReportResources.xaml", UriKind.Relative)) as ResourceDictionary;
UIElement adornerElement = reportResourceDictionary["RefreshingReportAdorner"] as UIElement;
mainWindowBlockMessageAdorner = new MainWindowBlockMessageAdorner(mainPanel, adornerElement);
But i want to update that text in textblock in some scenarios say if i click on some button in other window but how to update the text dynamically??
Adorner element from Resource file-
<Grid x:Key="RefreshingReportAdorner">
<Rectangle Fill="Gray"
StrokeThickness="1"
Stroke="Gray"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"/>
<Border BorderBrush="Black"
BorderThickness="2"
Background="White"
HorizontalAlignment="Center"
VerticalAlignment="Center">
<TextBlock i18n:LanguageManager.VisualId="6"
Text="Some Text(Update dynamically)"
Padding="15,10,15,10"/>
</Border>
</Grid>
Let me know if additional code or approach required..
Have you tried to create some model and push it to RefreshingReportAdorner element's DataContext?
Code:
var reportResourceDictionary = App.LoadComponent(new Uri("Resources/ReportResources.xaml", UriKind.Relative)) as ResourceDictionary;
var adornerElement = reportResourceDictionary["RefreshingReportAdorner"] as FrameworkElement;
var model = new Model();
model.MyText = "Initial text";
adornerElement.DataContext = model;
mainWindowBlockMessageAdorner = new MainWindowBlockMessageAdorner(mainPanel, adornerElement);
...
model.MyText = "Text after click";
XAML:
<TextBlock i18n:LanguageManager.VisualId="6"
Text="{Binding MyText}"
Padding="15,10,15,10"/>
Model:
public class Item : INotifyPropertyChanged
{
private string _myText;
public string MyText
{
get
{
return this._myText;
}
set
{
this._myText= value;
this.OnPropertyChanged("MyText");
}
}
}
I have a ListView with ContextMenu on each ListViewItem that has Click event,
how can I detect in the event handler which Item was clicked in this ContextMenu?
I need the item ID.
<Style TargetType="{x:Type ListViewItem}">
.
.
.
<Setter Property="Control.Template">
<Setter.Value>
<ControlTemplate TargetType="tv:TreeListViewItem">
<Grid>
<Grid.ContextMenu>
<ContextMenu>
<MenuItem Header="Open in current tab" Click="MenuItemCurrentTab_Click"/>
<MenuItem Header="Open in new tab" Click="MenuItemNewTab_Click"/>
</ContextMenu>
</Grid.ContextMenu>
See this thread..
Following the same way as the answer from the link you would
<Grid.ContextMenu>
<ContextMenu>
<MenuItem Header="Open in current tab"
Click="MenuItemCurrentTab_Click"
CommandParameter="{Binding RelativeSource={RelativeSource Self}, Path=Parent}"/>
...
private void MenuItemCurrentTab_Click(object sender, RoutedEventArgs e)
{
MenuItem menuItem = sender as MenuItem;
if (menuItem != null)
{
ContextMenu parentContextMenu = menuItem.CommandParameter as ContextMenu;
if (parentContextMenu != null)
{
ListViewItem listViewItem = parentContextMenu.PlacementTarget as ListViewItem;
}
}
}
UPDATE
Add this to get the parent ListViewItem from the Grid
public T GetVisualParent<T>(object childObject) where T : Visual
{
DependencyObject child = childObject as DependencyObject;
while ((child != null) && !(child is T))
{
child = VisualTreeHelper.GetParent(child);
}
return child as T;
}
private void MenuItemCurrentTab_Click(object sender, RoutedEventArgs e)
{
MenuItem menuItem = sender as MenuItem;
if (menuItem != null)
{
ContextMenu parentContextMenu = menuItem.CommandParameter as ContextMenu;
if (parentContextMenu != null)
{
Grid grid = parentContextMenu.PlacementTarget as Grid;
ListViewItem listViewItem = GetVisualParent<ListViewItem>(grid);
}
}
}
private void MenuItemCurrentTab_Click(object sender, RoutedEventArgs e)
{
MenuItem menuItem = (MenuItem)e.Source;
ContextMenu menu = (ContextMenu)menuItem.Parent;
ListViewItem item = (ListViewItem)menu.PlacementTarget;
// do something with item
}
But it's probably better idea to create single ContextMenu, give it proper name, and use it for all list view items.
A recurring problem, with many attempts to solve but all have their drawbacks. The accepted answer here, for instance, supposes that each ListViewItem has its own ContextMenu. This works but, especially with a larger number of list items, has a considerable cost in XAML complexity and can be slow. And really isn't necessary at all. If we only use a single ContextMenu on the ListView itself, some other solutions suggest to use
<MenuItem CommandParameter="{Binding PlacementTarget.SelectedItem, RelativeSource={RelativeSource AncestorType=ContextMenu}}" />
which seems to solve the problem at first sight (PlacementTarget points to the ListView, its SelectedItem points to the list item, so the menu item handler can use the CommandParameter to get the originating list item), but, unfortunately, fails if the ListView has multiple selection enabled (SelectedItem will point to one of the items selected but not necessarily the one currently clicked) or if we use ListView.PreviewMouseRightButtonDown to disable the selection on right-click (which is, arguably, the only logical thing to do with multiple selections).
There is, however, an approach that has all the benefits:
single ContextMenu on the ListView itself;
works with all selection schemes, single, multiple, disabled;
even with multiple selection, it will pass the currently hovered item to the handler.
Consider this ListView:
<ListView ContextMenuOpening="ListView_ContextMenuOpening">
<ListView.ContextMenu>
<ContextMenu>
<MenuItem Header="Menu1" Click="Menu1_Click" CommandParameter="{Binding Parent, RelativeSource={RelativeSource Self}}" />
</ContextMenu>
</ListView.ContextMenu>
</ListView>
The CommandParameter is used to pass the parent of the MenuItem, ie. the ContextMenu itself. But the main trick comes in the menu opening handler:
private void ListView_ContextMenuOpening(object sender, ContextMenuEventArgs e) {
var menu = (e.Source as FrameworkElement).ContextMenu;
menu.Tag = (FrameworkElement)e.OriginalSource;
}
Inside this handler, we still know the original source of the event, the root FrameworkElement of the list item DataTemplate. Let's store it in the Tag of the menu for later retrieval.
private void Menu1_Click(object sender, RoutedEventArgs e) {
if (sender is MenuItem menu)
if (menu.CommandParameter is ContextMenu context)
if (context.Tag is FrameworkElement item)
if (item.DataContext is DataType data) {
//process data
}
}
In the menu click handler, we can look up the original ContextMenu we stored in the command parameter, from that we can look up the root FrameworkElement of the list item that we stored just before, and finally get the object stored in the list item (of type DataType).
ListViewItem item = myListView.SelectedItem as ListViewItem;
Seems to work just fine as the item is selected when you right-click it.
I'm writing an application in WPF using the MVVM pattern. In my application I've got an IPopupWindowService which I use to create a popup dialog window.
So to show a ViewModel in a popup window you'd do something like this:
var container = ServiceLocator.Current.GetInstance<IUnityContainer>();
var popupService = container.Resolve<IPopupWindowService>();
var myViewModel = container.Resolve<IMyViewModel>();
popupService.Show((ViewModelBase)myViewModel);
This is all well and good. What I want to do is be able to set the MinHeight and MinWidth on the View bound to myViewModel and have the popup window use those settings so that a user cannot make the window smaller than its contents will allow. At the moment when the user shrinks the window the contents stops resizing but the window doesn't.
EDIT:
I map my Views to my ViewModels in ResourceDictionarys like so:
<DataTemplate DataType="{x:Type ViewModels:MyViewModel}">
<Views:MyView />
</DataTemplate>
And my popup view looks like this:
<Window x:Class="TheCompany.Cubit.Shell.Views.PopupWindowView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
SizeToContent="WidthAndHeight"
WindowStartupLocation="CenterOwner">
<DockPanel x:Name="panelContent">
<StackPanel HorizontalAlignment="Right" DockPanel.Dock="Bottom" Orientation="Horizontal" Visibility="{Binding RelativeSource={RelativeSource AncestorType=Window},Path=ButtonPanelVisibility}">
<Button Width="75" IsDefault="True" x:Uid="ViewDialog_AcceptButton" Click="OnAcceptButtonClick" Margin="4">OK</Button>
<Button Width="75" IsCancel="True" x:Uid="ViewDialog_CancelButton" Click="OnCancelButtonClick" Margin="0,4,4,4">Cancel</Button>
</StackPanel>
<ContentPresenter Content="{Binding}" />
</DockPanel>
You can define MinHeight and MinWidth properties on your ViewModel and use databinding to bind the View to those properties in XAML:
<...
MinHeight="{Binding Path=MinHeight}"
MinWidth="{Binding Path=MinWidth}"
.../>
I designed exactly the same generic modal dialog control (using Type-targeted DataTemplates) and also stumbled into this problem.
Using a RelativeSource does not work because you can only find ancestors that way (afaik).
Another possible solution was to name the ContentPresenter and bind to properties on that using ElementName binding. However, the ContentPresenter does not "inherit" the MinHeight and MinWidth properties from the Visual child it renders.
The solution eventually was to use VisualTreeHelper to get at the resolved View associated with the ViewModel at runtime:
DependencyObject lObj = VisualTreeHelper.GetChild(this.WindowContent, 0);
if (lObj != null && lObj is FrameworkElement)
{
lWindowContentMinHeight = ((FrameworkElement)lObj).MinHeight;
lWindowContentMinWidth = ((FrameworkElement)lObj).MinWidth;
}
I put this code in a OnActivated() override in the code-behind of the ModalDialogView (in OnInitialized the View can't be resolved yet).
The only remaining issue is to correct these minimums so that the window width and button panel height is taken into account.
UPDATE
Below is the code I use in my generic modal dialog. It solves the following additional problems:
It centers on the owner window correctly
It doesn't do anything if the content doesn't have MinWidth and MinHeight set
private bool _MinSizeSet = false;
public ModalDialogView(object pDataContext)
: this()
{
this.DataContext = pDataContext;
this.LayoutUpdated += new EventHandler(ModalDialogView_LayoutUpdated);
}
void ModalDialogView_LayoutUpdated(object sender, EventArgs e)
{
if (System.Windows.Media.VisualTreeHelper.GetChildrenCount(this.WindowContent) > 0)
SetInitialAndMinimumSize();
}
private void SetInitialAndMinimumSize()
{
FrameworkElement lElement = VisualTreeHelper.GetChild(this.WindowContent, 0) as FrameworkElement;
if (!_MinSizeSet && lElement != null)
{
if (lElement.MinWidth != 0 && lElement.MinHeight != 0)
{
double lHeightDiff = this.ActualHeight - this.WindowContent.ActualHeight;
double lWidthDiff = this.ActualWidth - this.WindowContent.ActualWidth;
this.MinHeight = lElement.MinHeight + lHeightDiff;
this.MinWidth = lElement.MinWidth + lWidthDiff;
this.SizeToContent = SizeToContent.Manual;
this.Height = this.MinHeight;
this.Width = this.MinWidth;
this.Left = this.Owner.Left + (this.Owner.ActualWidth - this.ActualWidth) / 2;
this.Top = this.Owner.Top + (this.Owner.ActualHeight - this.ActualHeight) / 2;
}
_MinSizeSet = true;
}
}
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.
I have a list of strings displayed by a Silverlight ItemsControl. The DataTemplate is a Border control with a TextBlock as its child. How can I access the border control corresponding to an item? For example, I might want to do this to change the background color.
An easier way to do this is to grab the Parent of the textblock and cast it as a Border. Here is a quick example of this:
Xaml
<Grid>
<ItemsControl x:Name="items">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Border>
<TextBlock MouseEnter="TextBlock_MouseEnter" MouseLeave="TextBlock_MouseLeave" Text="{Binding}" />
</Border>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Grid>
Code behind
public Page()
{
InitializeComponent();
items.ItemsSource = new string[] { "This", "Is", "A", "Test" };
}
private void TextBlock_MouseEnter(object sender, MouseEventArgs e)
{
var tx = sender as TextBlock;
var bd = tx.Parent as Border;
bd.Background = new SolidColorBrush(Colors.Yellow);
}
private void TextBlock_MouseLeave(object sender, MouseEventArgs e)
{
var tx = sender as TextBlock;
var bd = tx.Parent as Border;
bd.Background = new SolidColorBrush(Colors.White);
}
The example sets the background on the border by grabbing the parent of the textbox.
You can override the ItemsControl.GetContainerForItemOverride method and save the object-container pairs in a dictionary.
see this: http://msdn.microsoft.com/en-us/library/bb613579.aspx and this: http://blogs.msdn.com/wpfsdk/archive/2007/04/16/how-do-i-programmatically-interact-with-template-generated-elements-part-ii.aspx. Unfortunately, it won't work in SL because SL DataTemplate class doesn't have the FindName method.