I am trying to add UI elements dynamically, but I am facing a problem, I able to add UI elements, but I fail to add click event to Button
Below is my code :
ParserContext context = new ParserContext();
context.XmlnsDictionary.Add("", "http://schemas.microsoft.com/winfx/2006/xaml/presentation");
context.XmlnsDictionary.Add("x", "http://schemas.microsoft.com/winfx/2006/xaml");
string xaml = String.Format(" <StackPanel Orientation='Vertical'>");
xaml = xaml + "<StackPanel Orientation='Horizontal'>";
xaml = xaml + "<Button Margin='5,5,0,0' Background='AliceBlue' Foreground='DarkBlue' Height='25' VerticalAlignment='Bottom' HorizontalAlignment='Right' Width='82' Tag='12' Click='btnMy_Click'>";
xaml = xaml + "<StackPanel Orientation='Horizontal'>";
xaml = xaml + "<Image Source='/MotionTest;component/images/open.png' Width='18' Height='18' />";
xaml = xaml + "<TextBlock Text=' Open' VerticalAlignment='Center' FontSize='13' />";
xaml = xaml + "</StackPanel>";
xaml = xaml + "</Button>";
xaml = xaml + "</StackPanel>";
xaml = xaml + "</StackPanel>";
UIElement element = (UIElement)XamlReader.Parse(xaml, context);
myTestGrid.Children.Add(element);
And my onClick function :
private void btnMy_Click(object sender, RoutedEventArgs e)
{
var myValue = ((Button)sender).Tag;
MessageBox.Show("Here = " + myValue);
}
For this line :
xaml = xaml + "<Button Margin='5,5,0,0' Background='AliceBlue' Foreground='DarkBlue' Height='25' VerticalAlignment='Bottom' HorizontalAlignment='Right' Width='82' Tag='12' Click='btnMy_Click'>";
If I remove the
Click='btnMy_Click'
It will work. But if I add it, It shows
Anyone know how to solve this ?
Thanks in advance.
Since the error message states that to specify events, you need to compile your XAML file, and you specifically do not want to do that, it seems to be impractical (you may be able to compile a temporary assembly and load it, but then you can't unload it again).
What you could do is give the element a name:
xaml = xaml + "<Button x:Name='ClickMe'>";
And then, using a helper function to retrieve it:
var button = UIHelper.FindChild<Button>(element, "ClickMe");
button.Click += btnMy_Click;
The helper function looks like this, I took it from How can I find WPF controls by name or type? :
/// <summary>
/// Finds a Child of a given item in the visual tree.
/// </summary>
/// <param name="parent">A direct parent of the queried item.</param>
/// <typeparam name="T">The type of the queried item.</typeparam>
/// <param name="childName">x:Name or Name of child. </param>
/// <returns>The first parent item that matches the submitted type parameter.
/// If not matching item can be found,
/// a null parent is being returned.</returns>
public static T FindChild<T>(DependencyObject parent, string childName)
where T : DependencyObject
{
// Confirm parent and childName are valid.
if (parent == null) return null;
T foundChild = null;
int childrenCount = VisualTreeHelper.GetChildrenCount(parent);
for (int i = 0; i < childrenCount; i++)
{
var child = VisualTreeHelper.GetChild(parent, i);
// If the child is not of the request child type child
T childType = child as T;
if (childType == null)
{
// recursively drill down the tree
foundChild = FindChild<T>(child, childName);
// If the child is found, break so we do not overwrite the found child.
if (foundChild != null) break;
}
else if (!string.IsNullOrEmpty(childName))
{
var frameworkElement = child as FrameworkElement;
// If the child's name is set for search
if (frameworkElement != null && frameworkElement.Name == childName)
{
// if the child's name is of the request name
foundChild = (T)child;
break;
}
}
else
{
// child element found.
foundChild = (T)child;
break;
}
}
return foundChild;
}
Related
I generate DataTemplate in code to pass it into a GridViewColumn of a GridView of a ListView:
private static DataTemplate CreateTemplate(string sourceProperty)
{
string Xaml = "<DataTemplate xmlns=\"http://schemas.microsoft.com/winfx/2006/xaml/presentation\">" +
" <DataTemplate.Resources>" +
" <Style TargetType=\"DockPanel\">" +
" <Setter Property=\"HorizontalAlignment\" Value=\"Stretch\" />" +
" <Style.Triggers>" +
" <Trigger Property=\"IsMouseOver\" Value=\"True\">" +
" <Setter Property=\"Background\" Value=\"Black\"/>" +
" </Trigger>" +
" <Trigger Property=\"IsMouseOver\" Value=\"False\">" +
" <Setter Property=\"Background\" Value=\"White\"/>" +
" </Trigger>" +
" </Style.Triggers>" +
" </Style>" +
" </DataTemplate.Resources>" +
" <DockPanel x:Name=\"cellPanel\">" +
" <TextBlock Text=\"{Binding " + sourceProperty + "}\"/>" +
" </DockPanel>" +
" </DataTemplate>";
return XamlReader.Parse(Xaml) as DataTemplate;
}
I need to subscribe to mouse events of the DockPaneland cannot do it via parser, since it doesn't work. A workaround I found is to find the DockPanel by name (which is "cellPanel") and subscribe manually. How can I do it? Here is my method for fillinga ListView with columns and templates:
private void FillListView(DataTable table)
{
GridView grid = (GridView)lvMain.View;
foreach (DataColumn col in table.Columns)
{
DataTemplate cellTemplate = CreateTemplate(col.ColumnName);
var gridColumn = new GridViewColumn()
{
Header = col.ColumnName,
CellTemplate = cellTemplate
};
grid.Columns.Add(gridColumn);
}
lvMain.HorizontalContentAlignment = HorizontalAlignment.Stretch;
lvMain.ItemsSource = ((IListSource)table).GetList();
}
I could surely use TemplateFramework.FindName method, but both GridView and GridViewColumn are not FrameworkElements.
The template is not loaded at the point of creation. It will be loaded once the ListViewItems are loaded.
Since the GridViewColumn is just the model for the actual ListViewItem and not part of the visual tree (it doesn't even extend FrameworkElement), you can't access the GridViewColumn or GridViewColumn.CellTemplate directly. The actual cell is placed inside a GridViewRowPresenter of the ListViewItem.
The solution is to iterate over all items, once the ListView is completely loaded and all its items are displayed:
private void FillListView(DataTable table)
{
GridView grid = (GridView)lvMain.View;
foreach (DataColumn col in table.Columns)
{
DataTemplate cellTemplate = CreateTemplate(col.ColumnName);
var gridColumn = new GridViewColumn()
{
Header = col.ColumnName,
CellTemplate = cellTemplate
};
grid.Columns.Add(gridColumn);
}
lvMain.HorizontalContentAlignment = HorizontalAlignment.Stretch;
lvMain.ItemsSource = ((IListSource)table).GetList();
HandleGridViewColumns(lvMain);
}
private void HandleGridViewColumns(ListView listView)
{
foreach (var item in listView.Items)
{
DependencyObject itemContainer = listView.ItemContainerGenerator.ContainerFromItem(item);
// Each item corresponds to one row, which contains multiple cells
foreach (ContentPresenter cellContent in FindVisualChildElements<ContentPresenter>(itemContainer))
{
if (!cellContent.IsLoaded)
{
cellContent.Loaded += OnCellContentLoaded;
continue;
}
SubscribeToDockPanel(cellContent);
}
}
}
private void OnCellContentLoaded(object sender, RoutedEventArgs e)
{
SubscribeToDockPanel(sender as DependencyObject);
}
private void SubscribeToDockPanel(DependencyObject visualParent)
{
if (TryFindVisualChildElementByName(visualParent, "cellPanel", out FrameworkElement element))
{
var dockPanel = element as DockPanel;
// Handle DockPanel
}
}
private IEnumerable<TChild> FindVisualChildElements<TChild>(DependencyObject parent)
where TChild : DependencyObject
{
if (parent is Popup popup)
{
parent = popup.Child;
if (parent == null)
{
yield break;
}
}
for (var childIndex = 0; childIndex < VisualTreeHelper.GetChildrenCount(parent); childIndex++)
{
DependencyObject childElement = VisualTreeHelper.GetChild(parent, childIndex);
if (childElement is TChild child)
{
yield return child;
}
foreach (TChild childOfChildElement in FindVisualChildElement<TChild>(childElement))
{
yield return childOfChildElement;
}
}
}
private bool TryFindVisualChildElementByName(DependencyObject parent, string childElementName, out FrameworkElement resultElement)
{
resultElement = null;
if (parent is Popup popup)
{
parent = popup.Child;
if (parent == null)
{
return false;
}
}
for (var childIndex = 0; childIndex < VisualTreeHelper.GetChildrenCount(parent); childIndex++)
{
DependencyObject childElement = VisualTreeHelper.GetChild(parent, childIndex);
if (childElement is FrameworkElement uiElement && uiElement.Name.Equals(
childElementName,
StringComparison.OrdinalIgnoreCase))
{
resultElement = uiElement;
return true;
}
if (TryFindVisualChildElementByName(childElement, childElementName, out resultElement))
{
return true;
}
}
return false;
}
The above solution will generally work, but will fail in a scenario where UI virtualization is enabled (which is the default for the ListView). The result of UI virtualization is, that items that are not realized, won't have a item container generated. ItemGenerator.ContainerFromItem would return null in this case, which means the template is not applied and its visual tree therefore not loaded and part of the application tree.
I may update the answer later, to show how to access the item's container template in an UI virtualization context.
But as your primary goal was to attach mouse event handlers to the DockPanel, I recommend a different solution.
UIElement events are Routed Events (which, according to best practice, come in tandem with an instance event wrapper).
Routed Events have the advantage, that they don't require the observer to subscribe to the instance which raises the event. This eliminates code complexity. Instance events introduce the necessity to handle instance life-cycles and their attached event handlers, as instances are created and removed dynamically from/to the visual tree (e.g. TabControl, UI virtualization). Additionally Routed Events don't require any knowledge of the visual tree in order to handle them.
Routed Events work differently. The dependency property system will traverse the visual tree and invoke registered RoutedEventHandler delegates for specific events.
The recommended solution is therefore to subscribe to the required Routed Event e.g. UIElement.PreviewLeftMouseButtonUp.
Because Routed Events are realized by the framework's dependency property system, the listener must be a DependencyObject.
The following example registers an event handler for the UIElement.PreviewLeftMouseButtonUp Routed Event, which will only handle events, that originate from any child element of a DockPanel named CellPanel:
C#
public MainWindow()
{
AddHandler(UIElement.MouseLeftButtonUpEvent, new MouseButtonEventHandler(OnUIElementClicked));
}
XAML
<MainWindow MouseLeftButtonUp="OnUIElementClicked" />
MainWindow.xaml.cs
private void OnUIElementClicked(object sender, MouseButtonEventArgs e)
{
// Check if the event source is a child of the DockPanel named CellPanel
if (TryFindVisualParentElementByName(e.OriginalSource as DependencyObject, "CellPanel", out FrameworkElement element))
{
// Handle DockPanel clicked
}
}
private bool TryFindVisualParentElementByName(DependencyObject child, string elementName, out FrameworkElement resultElement)
{
resultElement = null;
if (child == null)
{
return false;
}
var parentElement = VisualTreeHelper.GetParent(child);
if (parentElement is FrameworkElement frameworkElement
&& frameworkElement.Name.Equals(elementName, StringComparison.OrdinalIgnoreCase))
{
resultElement = frameworkElement;
return true;
}
return TryFindVisualParentElementByName(parentElement, elementName, out resultElement);
}
Remarks
Since you usually want to do something UI related, when a UI event was raised, I recommend to handle Routed Events using an EventTrigger. They can be defined in XAML, which makes the code more easier to read (as their markup syntax is simpler than the C# syntax) and eliminates the need to manually traverse the visual tree. If you need access to elements of a template, you should move the triggers inside it.
From within the template's scope, you can easily target any named element:
<DataTemplate>
<DataTemplate.Triggers>
<EventTrigger RoutedEvent="UIElement.MouseEnter"
SourceName="CellPanel">
<BeginStoryBoard>
<StoryBoard>
<DoubleAnimation Storyboard.TargetName="CellText"
Storyboard.TargetProperty="Opacity" ... />
</StoryBoard>
</BeginStoryBoard>
</EventTrigger>
</DataTemplate.Triggers>
<DockPanel x:Name="CellPanel">
<TextBlock x:Name="CellText" />
...
</DockPanel>
</DataTemplate>
If you need to do more complex operations, like selecting all text of a TextBox on MouseEnter, you should write an Attached Behavior using attached properties.
Im currently trying to get drag/drop within a treeview working(using databinding and HierarchicalDataTemplate), and have the dragging working, but im running into a problem when trying to get the drop working, since i need to get the data item being dropped on and add it to its item collection of children nodes.
//treeitem is the name of my item data class
private void TreeViewItem_Drop(object sender, DragEventArgs e)
{
if (e.Data.GetDataPresent("DragableTreeViewItem"))
{
//the data of the treeitem i need to duplicate, provided by the drag operation
TreeItem data = e.Data.GetData("DragableTreeViewItem") as TreeItem;
TreeViewItem tvi = sender as TreeViewItem;
//here im getting the treeviewitem, but i need the treeitem
}
}
My best idea on how to get around this was to assign the treeitem as the tag of the treeviewitem on initialization/loading of the tree, and to do so i need to get the itemcontainer of the item, which i already have a working function to get that which i use when initiating the DoDrag()
//returns the item container of the parent of the TreeItem given
private TreeViewItem GetParentContainerFromItem(TreeItem ti)
{
List<TreeItem> GetOrderedParents(TreeItem item)
{
TreeItem currentParent = item;
List<TreeItem> items = new List<TreeItem>();
int i = 0;
do
{
if (currentParent.parentItem != null)
{
items.Insert(0, currentParent);
currentParent = currentParent.parentItem;
}
else
{
items.Insert(0, currentParent);
i++;
return items;
}
} while (i == 0);
return null;
}
//the local tree in a list, ordered from the original item (in this case "ti") at 0, down to the root at the end of the list
List<TreeItem> LocalHierarchy = GetOrderedParents(ti);
if (LocalHierarchy != null)
{
//print out the names of each treeitem in it, in order from the root down
string hierarchyString = "";
foreach (TreeItem t in LocalHierarchy)
{
if (hierarchyString == "")
{
hierarchyString = t.Title;
}
else
{
hierarchyString = (hierarchyString + ", " + t.Title);
}
}
System.Console.WriteLine(hierarchyString);
TreeViewItem localCurrentParent = null;
TreeViewItem finalContainer = null;
//walk down the tree in order to get the container of the parent
foreach (TreeItem t in LocalHierarchy)
{
//if the parent of the item given is a root node, meaning we can return its container
if (LocalHierarchy.IndexOf(t) == 0 && (LocalHierarchy.IndexOf(t) == (LocalHierarchy.Count - 2)))
{
finalContainer = treeView.ItemContainerGenerator.ContainerFromItem(t) as TreeViewItem;
break;
}
else
//if we're at a root node
if (LocalHierarchy.IndexOf(t) == 0)
{
localCurrentParent = treeView.ItemContainerGenerator.ContainerFromItem(t) as TreeViewItem;
}
else
//if we're at the 2nd to last, AKA the parent of the item given
if (LocalHierarchy.IndexOf(t) == (LocalHierarchy.Count - 2))
{
finalContainer = localCurrentParent.ItemContainerGenerator.ContainerFromItem(t) as TreeViewItem;
break;
}
else
{
localCurrentParent = localCurrentParent.ItemContainerGenerator.ContainerFromItem(t) as TreeViewItem;
}
}
if (finalContainer == null)
{
System.Console.WriteLine("Final container is null");
}
return finalContainer;
}
else
{
System.Console.WriteLine("ERROR: LocalHierarchy is null");
return null;
}
}
This seems to work perfectly when using to start the DoDrag()
private void DoDrag()
{
if(selectedItem != null)
{
TreeItem t = selectedItem;
TreeViewItem tvi = null;
if (t.parentItem == null)
{
//it has no parent, and is a root node
tvi = treeView.ItemContainerGenerator.ContainerFromItem(t) as TreeViewItem;
}
else
{
//it has a parent, and i can get the container for the parent
tvi = GetParentContainerFromItem(t).ItemContainerGenerator.ContainerFromItem(t) as TreeViewItem;
}
DragDrop.DoDragDrop(tvi, new DataObject("DragableTreeViewItem", t, true), DragDropEffects.Copy);
dragNeeded = false;
}
else if(selectedItem == null)
{
Console.WriteLine("Selected item was null; cant drag");
}
}
But when i try to use it in my function to assign container tags it says that Container was null and that GetParentContainerFromItem returned null, yet my function does not log that it returned null
private void AssignContainerTag(TreeItem t)
{
TreeViewItem Container = null;
if (t.parentItem == null)
{
//its a root node
Container = treeView.ItemContainerGenerator.ContainerFromItem(t) as TreeViewItem;
Container.Tag = t;
}
else
{
//it is not a root node
Container = GetParentContainerFromItem(t).ItemContainerGenerator.ContainerFromItem(t) as TreeViewItem;
Container.Tag = t;
}
}
Ive spent days stumped on why this does not seem to work, so if someone could give me some pointers on what im doing wrong or another way i could get the item from its container it would be a lifesaver. Also please excuse if this is poorly written, i am exhausted and about to go to sleep.
Your question is a little bit confusing. But it looks like you are trying to get the data item of the TreeViewItem which is the drop target of the drag&drop operation.
This is pretty simple. All you need to know is that if the item container is auto-generated via data binding (ItemsControl.ItemsSource) the DataContext of the container is the data item itself.
This applies to all item containers of an ItemsControl (e.g., ComboBoxItem, ListBoxItem, ListViewItem).
So TreeViewItem.DataContext references the underlying TreeItem instance that is wrapped by the TreeViewItem:
private void TreeViewItem_Drop(object sender, DragEventArgs e)
{
if (e.Data.GetDataPresent("DragableTreeViewItem"))
{
var sourceItem = e.Data.GetData("DragableTreeViewItem") as TreeItem;
var dropTargetItemContainer = sender as TreeViewItem;
var dropTargetItem = targetItemContainer.DataContext as TreeItem;
}
}
Remark
It looks like you are using the ItemContainerGenerator wrong. TreeView.ItemContainerGenerator will only handle top level items (i.e. child items). But as a tree node can have child nodes, each TreeViewItem is itself an ItemsControl as it contains an ItemsPresenter to display child items.
Therefore you have to use the appropriate ItemContainerGenerator to retrieve the child container or ItemContainerGenerator will return null.
For top-level items use TreeView.ItemContainerGenerator.
For child items use the parent's TreeViewItem.ItemContainerGenerator.
Also, in case of UI virtualization is enabled not all containers are generated when the TreeView is loaded. They are generated when need e.g., for display. Those containers (the TreeViewItem) are also shared to save resources. So once you set the TreeViewItem.Tag property its value might get lost as a new TreeViewItem instance is generated later to wrap the data item.
So you start at the root node and get its generated container. Now perform a tree search by traversing the TreeViewItems using a specific algorithm until you found the node where the DataContext equals the data item you are looking for and e.g., modify the Tag property.
You access the children of e.g. treeViewItemA by referencing the treeViewItemA.Items property and get their containers by calling treeVieItemA.ItemContainerGenerator.ContainerFromItem method for each child:
Example
public static class MyExtensions
{
// Get item container of item from TreeView, TreeViewItem, ListView or any ItemsControl
public static bool TryGetContainerOfChildItem<TItemContainer>(this ItemsControl itemsControl, object item, out TItemContainer itemContainer) where TItemContainer : DependencyObject
{
itemContainer = null;
foreach (object childItem in itemsControl.Items)
{
if (childItem == item)
{
itemContainer = (TItemContainer) itemsControl.ItemContainerGenerator.ContainerFromItem(item);
return true;
}
DependencyObject childItemContainer = itemsControl.ItemContainerGenerator.ContainerFromItem(childItem);
if (childItemContainer is ItemsControl childItemsControl && childItemsControl.TryGetContainerOfChildItem(item, out itemContainer))
{
return true;
}
}
return false;
}
}
Usage
// Search whole TreeView
if (treeView.TryGetContainerOfChildItem(item, out TreeViewItem itemContainer)
{
...
}
// Search from a specific parent TreeViewItem node
if (treeViewItem.TryGetContainerOfChildItem(item, out TreeViewItem itemContainer)
{
...
}
I am using the DateTimePicker WPF control from the Extended WPF Toolkit Community Edition library version 2.5.
My problem is that when I pick a date, the OnValueChanged event is raised twice instead of just once.
Here is the code I am using:
XAML:
<StackPanel>
<xctk:DateTimePicker AutoCloseCalendar="True" Name="picker" Width="400" Height="40" ValueChanged="UpDownBase_OnValueChanged"/>
<ListBox Height="300" Name="listbox"></ListBox>
</StackPanel>
C# code behind:
private void UpDownBase_OnValueChanged(object sender, RoutedPropertyChangedEventArgs<object> e)
{
var value = picker.Value;
if (value == null)
listbox.Items.Add("[NULL]");
else
listbox.Items.Add(value.Value.ToString(CultureInfo.InvariantCulture));
}
Now, whenever I pick a new date, the list box will be populated with two new items. I also debugged the program and confirmed that the event handler is actually invoked twice.
How can I solve this issue?
UPDATE:
I tried version 2.4 and it seems that the issue is gone. It seems to me now that this might be a possible bug in version 2.5.
This would appear to be because the event in 2.5 is being fired from:
at Xceed.Wpf.Toolkit.DateTimePicker.OnValueChanged(Nullable`1 oldValue, Nullable`1 newValue) in C:\Users\Mark Vinten\Downloads\wpftoolkit-114314\Main\Source\ExtendedWPFToolkitSolution\Src\Xceed.Wpf.Toolkit\DateTimePicker\Implementation\DateTimePicker.cs:line 264
And then subsequently from the base class:
at Xceed.Wpf.Toolkit.TimePicker.OnValueChanged(Nullable`1 oldValue, Nullable`1 newValue) in C:\Users\Mark Vinten\Downloads\wpftoolkit-114314\Main\Source\ExtendedWPFToolkitSolution\Src\Xceed.Wpf.Toolkit\TimePicker\Implementation\TimePicker.cs:line 264
Now the base class, also seems to go through the CLR binding process suggesting that this is the bound value. I'm still looking into why that would be, but a workaround is to use Binding as such:
MainWindow.cs
public DateTime? DateTimeValue
{
get { return (DateTime?)GetValue(DateTimeValueProperty); }
set { SetValue(DateTimeValueProperty, value); }
}
// Using a DependencyProperty as the backing store for DateTimeValue. This enables animation, styling, binding, etc...
public static readonly DependencyProperty DateTimeValueProperty =
DependencyProperty.Register("DateTimeValue", typeof(DateTime?), typeof(MainWindow), new PropertyMetadata(null, new PropertyChangedCallback(DateTimeValueProperty_Changed)));
private static void DateTimeValueProperty_Changed(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
MainWindow mw = d as MainWindow;
System.Diagnostics.Debug.WriteLine("d is " + d == null ? "null" : d.GetType().FullName);
if (mw != null && e.Property == DateTimeValueProperty)
{
var value = e.NewValue as DateTime?;
var listbox = FindChild<ListBox>(mw, "listbox");
if (value == null)
listbox.Items.Add("[NULL]");
else
listbox.Items.Add(value.Value.ToString(System.Globalization.CultureInfo.InvariantCulture));
}
}
/// <summary>
/// Finds a Child of a given item in the visual tree.
/// </summary>
/// <param name="parent">A direct parent of the queried item.</param>
/// <typeparam name="T">The type of the queried item.</typeparam>
/// <param name="childName">x:Name or Name of child. </param>
/// <returns>The first parent item that matches the submitted type parameter.
/// If not matching item can be found,
/// a null parent is being returned.</returns>
public static T FindChild<T>(DependencyObject parent, string childName)
where T : DependencyObject
{
// Confirm parent and childName are valid.
if (parent == null) return null;
T foundChild = null;
int childrenCount = VisualTreeHelper.GetChildrenCount(parent);
for (int i = 0; i < childrenCount; i++)
{
var child = VisualTreeHelper.GetChild(parent, i);
// If the child is not of the request child type child
T childType = child as T;
if (childType == null)
{
// recursively drill down the tree
foundChild = FindChild<T>(child, childName);
// If the child is found, break so we do not overwrite the found child.
if (foundChild != null) break;
}
else if (!string.IsNullOrEmpty(childName))
{
var frameworkElement = child as FrameworkElement;
// If the child's name is set for search
if (frameworkElement != null && frameworkElement.Name == childName)
{
// if the child's name is of the request name
foundChild = (T)child;
break;
}
}
else
{
// child element found.
foundChild = (T)child;
break;
}
}
return foundChild;
}
MainWindow.xaml
<StackPanel DataContext="{Binding RelativeSource={RelativeSource AncestorType={x:Type local:MainWindow}}}">
<xctk:DateTimePicker AutoCloseCalendar="True" Name="picker" Width="400" Height="40" Value="{Binding DateTimeValue}" />
<ListBox Height="300" Name="listbox"></ListBox>
</StackPanel>
This uses the binding system which automatically checks whether the value has changed and only raises events if it has.
Note: FindChild<> was a function that I found on this How can I find WPF controls by name or type? post
Update with final summary
The reason for this appears to be is because there is a TimePicker embedded within the DateTimePicker to provide the functionality. Unfortunately, both DateTimePicker and TimePicker derive from the same base and thus raise the same routed event within UpDownBase where T is DateTime?.
if you check on the event arguments, e.RoutedEVent is always UpDownBase.OnValueChanged since this is the class raising the event. e.Source or e.OriginalSource is always the DateTimePicker itself meaning you have no useful way to filter out one or the other event.
There is code within DateTimeUpDown.RaiseValueChangedEvent() to check if the TemplatedParent is a TimePicker to prevent re-raising but whether the event is raised from the DateTimePicker or the TimePicker the TemplatedParent always seems to be the DateTimePicker so that fails thus you get the event twice.
I have raised a bug with the findings on the WPFToolkit project site:
https://wpftoolkit.codeplex.com/workitem/22014
I solved this problem/bug by checking the originalsource of the event.
if(e.OriginalSource is Xceed.Wpf.Toolkit.DateTimePicker)
{
if(((Xceed.Wpf.Toolkit.DateTimePicker)e.OriginalSource).IsFocused == true)
{
ResetDataTable();
}
}
Since I don't display the timepicker in this control, I also make sure that it's the datetimepicker that has focus, and not the timepicker. Might be redundant
I solved this by comparing the original source and the source.
e.OriginalSource == e.Source
I've been working on an application months ago, my friend and i wanted to share this application with other friends, and i need to display some help to make the application easier because it was designed only for both of us.
The idea that came out is to display help on a text Block every time a hover event is popped from a button. So we added a textBlock. Now the problem that we still facing is how to create the Hover Event for every button in our Main Window, there are a lots of buttons in this window, So we can't add an event to every button in the XAML code.
What i am expecting from this answer is a way to add Hover Event to all buttons in the main window Programmatically ?
EDIT: after some googling and help, i can do the following :
foreach (UIElement btn in GeneralMenuGrid.Children)
{
if (btn is Button)
{
Button currentButton = (Button)btn;
currentButton.Content = "test";
}
}
This is just a test that will allow all the buttons in the GeneralMenuGrid control to have a content : test, now the problem again is that i have nested controls in this grid, how can i reach them?
EDIT : after years of goggling i got to loop through all the buttons in my window with this :
public static void EnumVisuals(Visual argVisual, Window currentWindow)
{
for (int i = 0; i < VisualTreeHelper.GetChildrenCount(argVisual); i++)
{
Visual childVisual = (Visual) VisualTreeHelper.GetChild(argVisual, i);
if (childVisual is Button)
{
var button = childVisual as Button;
button.MouseEnter += AllButtonsOnMouseEnter;
}
EnumVisuals(childVisual, currentWindow);
}
}
now in the AllButtonsOnMouseEnter function, i can't access a button, i made it public... i can't access it from this class, how can i send the window with the event arguments?
You wrote, "there are a lots of buttons in this window, so we can't add an event to every button in the XAML code." But you can - just add a style that applies to all buttons:
<Style TargetType="{x:Type Button}">
<EventSetter Event="MouseEnter" Handler="Button_MouseEnter"/>
</Style>
I don't know how you intend to get the help text relevant to each Button, but it's easy to store it in the Button's Tag:
<Button Tag="Instantly move from one place to another.">
Teleport
</Button>
Then write an event handler that shows the help in your TextBlock:
private void Button_MouseEnter(object sender, MouseEventArgs e)
{
Button button = sender as Button;
textblock_that_shows_help.Text = button.Tag;
}
I've created an extension method that does this, adapted from here: Find all controls in WPF Window by type
Put this class somewhere in your project:
public static class VisualTreeSearch
{
/// <summary>
/// Finds all elements of the specified type in the <see cref="System.Windows.DependencyObject"/>'s visual tree using a breadth-first search.
/// </summary>
/// <typeparam name="T">The type of element to search for.</typeparam>
/// <param name="root">The object to search in.</param>
/// <returns>A list of elements that match the criteria.</returns>
public static IEnumerable<T> Find<T>(this DependencyObject root) where T : DependencyObject
{
return root.Find<T>(false, true);
}
/// <summary>
/// Finds all elements of the specified type in the <see cref="System.Windows.DependencyObject"/>'s visual tree.
/// </summary>
/// <typeparam name="T">The type of element to search for.</typeparam>
/// <param name="root">The object to search in.</param>
/// <param name="depthFirst">True to do a depth-first search; false to do a breadth-first search</param>
/// <param name="includeRoot">True to include the root element in the search; false to exclude it</param>
/// <returns>A list of elements that match the criteria.</returns>
public static IEnumerable<T> Find<T>(this DependencyObject root, bool depthFirst, bool includeRoot) where T : DependencyObject
{
if (includeRoot)
{
var depRoot = root as T;
if (depRoot != null)
yield return depRoot;
}
var searchObjects = new LinkedList<DependencyObject>();
searchObjects.AddFirst(root);
while (searchObjects.First != null)
{
var parent = searchObjects.First.Value;
var count = VisualTreeHelper.GetChildrenCount(parent);
var insertAfterNode = depthFirst ? searchObjects.First : searchObjects.Last;
for (int i = 0; i < count; i++)
{
DependencyObject child = VisualTreeHelper.GetChild(parent, i);
var depChild = child as T;
if (depChild != null)
yield return depChild;
insertAfterNode = searchObjects.AddAfter(insertAfterNode, child);
}
searchObjects.RemoveFirst();
}
}
}
To use the extension method, write the following in your window class (as an example). This code loops through all children, grandchildren, etc. of this (which should be your Window in this case) and finds all Button elements.
foreach (var button in this.Find<Button>())
{
button.MouseEnter += button_MouseEnter;
}
...
private void button_MouseEnter(object sender, MouseEventArgs e)
{
// Do stuff
}
after a lot of googling and chat help... i finally did it, maybe other will be interested this is how i proceeded :
i created a static recursive function that will get all the buttons in the window:
public static void EnumVisuals(Visual argVisual, Button toModifyButton)
{
for (int i = 0; i < VisualTreeHelper.GetChildrenCount(argVisual); i++)
{
Visual childVisual = (Visual) VisualTreeHelper.GetChild(argVisual, i);
if (childVisual is Button)
{
var button = childVisual as Button;
button.MouseEnter += (sender, eventArgs) =>
{
toModifyButton.Content = "Get the Help text from database if you want";
};
}
EnumVisuals(childVisual, toModifyButton);
}
}
why do you send a Button :
i need to write the help in a button, and the only way i found to access it's content property is to send it via this function and of course make it public.
Hope you'll find this helpfull.
WPF default TreeView is scrolled to bottom of the node automatically where as we need to show the top view of the tree view. How to do that?
Also I could not get the scroll viewer by walking down the Visual Tree.
Preselect top node and call TreeViewItem.BringIntoView method on selection changed event. Call TreeView.ItemContainerGenerator.ContainerFromItem(e.NewValue) to get hold of the TreeViewItem.
This code is very rough.
The key to getting the TreeViewItem.BringIntoView() to get an item to the top, is to first scroll the TreeView to the bottom rather than the top.
To do this, we need to access the ScrollViewer inside the TreeView's control template first. Lots of messing around IMO, that should have been provided in the framework from the outset.
Your item control in this case, should be your TreeViewItem that you are trying to get to the top.
The uxTree control is the TreeView.
item.IsSelected = true;
ScrollViewer scroller = (ScrollViewer)this.FindVisualChildElement(this.uxTree, typeof(ScrollViewer));
scroller.ScrollToBottom();
item.BringIntoView();
private FrameworkElement FindVisualChildElement(DependencyObject element, Type childType)
{
int count = VisualTreeHelper.GetChildrenCount(element);
for (int i = 0; i < count; i++)
{
var dependencyObject = VisualTreeHelper.GetChild(element, i);
var fe = (FrameworkElement)dependencyObject;
if (fe.GetType() == childType)
{
return fe;
}
FrameworkElement ret = null;
if (fe.GetType().Equals(typeof(ScrollViewer)))
{
ret = FindVisualChildElement((fe as ScrollViewer).Content as FrameworkElement, childType);
}
else
{
ret = FindVisualChildElement(fe, childType);
}
if (ret != null)
{
return ret;
}
}
return null;
}