I'm trying to create a tree header within a TreeView, but nothing will display. In my program, I'm creating a series of ellipse objects on a canvas that have names. What I'm trying to do is this:
When the ellipse object is created, create a header within the TreeView using the ellipse' names.
Since the first ellipse has a name of "Circle01," this is what I'm trying to display in the TreeView when the Left Mouse button is released. But when I do so, nothing happens.
// Ellipse object is created when Left Mouse button is release.
private void _canvas_MouseLeftButtonUp(object sender, MouseButtonEventArgs e)
{
if (mouseLBDown)
{
mouseLBDown = false;
if (isCreatingEllipse)
{
_debugLabel.Content = "Done.";
isCreatingEllipse = false;
loadToTree = true;
}
}
}
// Suppose to load name of Ellipse as a header, but doesn't.
private void _circleTree_Loaded(object sender, RoutedEventArgs e)
{
if (loadToTree)
{
TreeViewItem item = new TreeViewItem();
item.Header = circle[0].circleName;
_circleTree.Items.Add(item);
loadToTree = false;
}
}
XAML:
<TreeView Name="_boneTree" Background="#404040" Foreground="#E0E0E0"
HorizontalAlignment="Left" Height="422" Margin="970,28,0,0"
VerticalAlignment="Top" Width="212" Loaded="_circleTree_Loaded"/>
You should not create your own TreeViewItems, rather set the TreeView's ItemsSource and let WPF build the TreeViewItems for you. Also it is easier to do this in XAML:
<TreeView Name="_boneTree" Background="#404040" Foreground="#E0E0E0"
HorizontalAlignment="Left" Height="422" Margin="970,28,0,0"
VerticalAlignment="Top" Width="212"
ItemsSource="{Binding Circles}">
<TreeView.Resources>
<Style TargetType="TreeViewItem">
<Setter Property="Header" Value="{Binding circleName}"/>
</Style>
</TreeView.Resources>
</TreeView>
Where Circles is a property on your DataContext which by default will be your codebehind:
public ObservableCollection<Circle> Circles { get; set; }
Then whenever a Circle is added or removed it will automatically show up on the UI!
Related
My application uses TreeView populated with custom nodes defined in TreeView.ItemTemplate. Content of each node is wrapped into StackPanel with Node_ContexMenuOpening event that populates context menu based on some application properties, which is working.
XAML:
<TreeView x:Name="treeNodes" ContextMenu="{StaticResource EmptyContextMenu}" ContextMenuOpening="TreeNodes_ContextMenuOpening">
<TreeView.ItemTemplate>
<HierarchicalDataTemplate DataType="{x:Type c:MyCustomType}" ItemsSource="{Binding MyCustomTypeChildren}">
<StackPanel Orientation="Horizontal" ContextMenu="{StaticResource EmptyContextMenu}" ContextMenuOpening="Node_ContextMenuOpening" >
<Image Source="Frontend\Images\import.png" MaxWidth="15" MaxHeight="15"/>
<TextBlock Width="5"/>
<TextBlock Text="{Binding CustomTypeName}" MinWidth="100"/>
<TextBlock Width="10"/>
<Image Source="CustomImagePath" MaxWidth="15" MaxHeight="15"/>
<TextBlock Width="5"/>
<TextBlock Text="{Binding CustomTypeName2}"/>
</StackPanel>
</HierarchicalDataTemplate>
</TreeView.ItemTemplate>
</TreeView>
Code behind:
private void Node_ContextMenuOpening(object sender, ContextMenuEventArgs e)
{
FrameworkElement fe = sender as FrameworkElement;
// get context menu and clear all items (empty menu with single placeholder item
// is assigned in XAML to prevent "no object instance" exception)
ContextMenu menu = fe.ContextMenu;
menu.Items.Clear();
// populate menu there
}
I would like to have same functionality on TreeView (treeview specific context menu when right clicking on empty area of treeview), which also works.
private void TreeNodes_ContextMenuOpening(object sender, ContextMenuEventArgs e)
{
TreeView tw = sender as TreeView;
ContextMenu menu = tw.ContextMenu;
menu.Items.Clear();
// poopulate menu there
}
But the issue is that TreeNodes_ContextMenuOpening is fired even after right clicking at TreeView node, right after Node_ContextMenuOpening is handled, which overwrites context menu for clicked node. I tried to solve it using:
// also tried IsMouseOver and IsMouseCaptureWithin
if (tw.IsMouseDirectlyOver)
{
// handle TreeNodes_ContextMenuOpening event there
}
but without success. Any suggestions?
Thanks in advance.
You can try using the ContextMenuEventArgs.Handled value. https://learn.microsoft.com/en-us/dotnet/api/system.windows.routedeventargs.handled?view=netcore-3.1#System_Windows_RoutedEventArgs_Handled
Gets or sets a value that indicates the present state of the event handling for a routed event as it travels the route.
Example
protected override void OnPreviewMouseRightButtonDown(System.Windows.Input.MouseButtonEventArgs e)
{
e.Handled = true; //suppress the click event and other leftmousebuttondown responders
MyEditContainer ec = (MyEditContainer)e.Source;
if (ec.EditState)
{ ec.EditState = false; }
else
{ ec.EditState = true; }
base.OnPreviewMouseRightButtonDown(e);
}
I have an ItemsControl that wraps its ItemPresenter with a ScrollViewer. That ItemPresenter displays a ListView. Therefore I have a collection within a collection.
Now, I want only the ScrollViewer to have scrolling functionality so I have gone ahead and removed the scrolling functionality from the inner ListView.
The problem is that my scrolling event is being messed up by the ListView. As soon as my finger touches the content area it selects the ListViewItems instead of scrolling.
How can I tell through routed events if the user is trying to click or scroll? and if it is scroll, how do I prevent it from selecting the ListViewItems?
<ItemsControl ItemsSource="{Binding Countries}" >
<ItemsControl.Template>
<ControlTemplate>
<ScrollViewer PanningMode="VerticalOnly">
<ItemsPresenter/>
</ScrollViewer>
</ControlTemplate>
</ItemsControl.Template>
<ItemsControl.ItemTemplate>
<DataTemplate>
<ListView ItemsSource="{Binding Cities}">
<ListView.Template>
<ControlTemplate>
<ItemsPresenter/>
</ControlTemplate>
</ListView.Template>
</ListView>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
There isn't any way to look into the future to see if a user is going to begin scrolling after touching the screen or simply lift their finger off immediately afterwards. A Future API would be nice, though.
Anyhow, you could just check to see if the user has moved their finger at all after touching the ListView. If so, begin treating the "touch" like a "scroll" rather than a "click" by manually scrolling the ScrollViewer and deselecting the ListView items.
Something like this:
private bool _touchDown = false;
private double _initOffset = 0;
private double _scrollDelta = 5;
private void ListView_PreviewTouchDown(object sender, TouchEventArgs e)
{
_touchDown = true;
_initOffset = e.GetTouchPoint(this).Y;
}
private void ListView_PreviewTouchMove(object sender, TouchEventArgs e)
{
if (_touchDown && Math.Abs(r.GetTouchpoint(this).Y - _initOffset) > _scrollDelta)
{
My_ScrollViewer.ScrollToVerticalOffset(r.GetTouchpoint(this).Y - _initOffset);
My_ListView.UnselectAll();
}
}
private void ListView_PreviewTouchUp(object sender, TouchEventArgs e)
{
_touchDown = false;
_initOffset = 0;
}
Disclaimer: I just wrote this in notepad. It has problems, but it gets the concept across.
I have defined a label with name and I'm trying to access it but no luck. Let me explain my problem with my code.
<ListView Name="gridListView" ItemsSource="{Binding... }">
<ListView.ItemContainerStyle>
<Style TargetType="ListViewItem">
<Setter Property="Focusable" Value="false"/>
</Style>
</ListView.ItemContainerStyle>
<ListView.ItemTemplate>
<DataTemplate>
<Border>
<StackPanel Orientation="Vertical">
<Label x:Name="vLabel" Content="{Binding VCValue}"/>
<ListView Name="checkBoxListView" ItemsSource="{Binding CList}">
<ListView.ItemTemplate>
<DataTemplate>
<CheckBox Margin="5" Click="CheckBox_Click" IsChecked="{Binding SelectedValue, Mode=TwoWay}" Content="{Binding Current, Mode=OneWay }"/>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</StackPanel>
</Border>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
In the above code, I have two listview, gridListView and checkBoxListView. Here, I want to access the label vLabel which is inside the datatemplate of gridListView when the one of the value in checkbox(which is inside checkBoxListView) is clicked.
I understand it can't be accessed directly as its within datatemplate so i tried below code as suggested in other forums but gridListView.SelectedIndex is always -1 so i know i'm not doing the right thing. When I just hardcoded gridListView.SelectedIndex to index 0, 1 or 2 its giving me the right value of vLabel so the below code will work if gridListView.SelectedIndex is correct.
private void CheckBox_Click(object sender, RoutedEventArgs e)
{
CheckBox chk =(CheckBox)sender;
int index = gridListView.Items.IndexOf(chk.DataContext);
ListViewItem item = gridListView.ItemContainerGenerator.ContainerFromIndex(gridListView.SelectedIndex) as ListViewItem;
if (item!=null)
{
//get the item's template parent
ContentPresenter templateParent = GetFrameworkElementByName<ContentPresenter>(item);
DataTemplate dataTemplate = gridListView.ItemTemplate;
if (dataTemplate != null && templateParent != null)
{
var lab = dataTemplate.FindName("vLabel", templateParent) as Label;
var v = lab.Content;
}
}
private static T GetFrameworkElementByName<T>(FrameworkElement referenceElement) where T : FrameworkElement
{
//I can post this function if need be
....
}
Appreciate any help that will help me access vLabel.
Thanks in advance
you are setting focusable to false, so you cant select the item by clicking. also the checkbox-checked happens before the selection, even if you were to set focusable to true, so selected index would still be -1.
you can find your label simply like this:
public Label FindLabel(CheckBox checkBox)
{
var listView = VisualTreeHelper.GetParent(checkBox);
while (listView.GetType() != typeof(ListView))
{
listView = VisualTreeHelper.GetParent(listView);
}
return (listView as FrameworkElement).FindName("vLabel") as Label;
}
but i suggest you tell us what you want to achieve, because this doesnt seem like a clean solution.
can you perhaps do this on your StackPanel:
<StackPanel CheckBox.Checked="CheckBox_Click" Orientation="Vertical">
and then access your two desired properties as following:
private void CheckBox_Click(object sender, RoutedEventArgs e)
{
var outerItem = (sender as FrameworkElement).DataContext;
var innerItem = (e.OriginalSource as FrameworkElement).DataContext;
}
and then do whatever you want to do?
Thank you for all your guidance. I did get hint from Milan's code to achieve solution for my issue. Here, I'm trying to get reference parent of listviewItem(i.e stackpanel) and then access its child. In my case, stack panels child at index 0 is Label and at index 1 is ListView. So then I go through visualtreehelper to get reference to its child at index 0 which is what i need access to. So here is the code snippet.
private void CheckBox_Click(object sender, RoutedEventArgs e)
{
CheckBox checkBox = (CheckBox)sender;
//to access parent of the checkbox
ListViewItem listViewItem =
GetVisualAncestor<ListViewItem>((DependencyObject)sender);
//to access parent of the listViewItem(which is parent of checkbox)
StackPanel stackPanel =
GetVisualAncestor<StackPanel>((DependencyObject)listViewItem);
int childCount = VisualTreeHelper.GetChildrenCount(stackPanel);
//to access child of stackpanel to access the current vLabel
var vValue = VisualTreeHelper.GetChild(stackPanel, 0) as Label;
}
private static T GetVisualAncestor<T>(DependencyObject o)where T : DependecyObject
{
...
}
I have a Universal Windows Application for a local bank, I'm working on a money transfer view, and they need to transfer money from account to account using the Drag and Drop feature in UWP applications.
I've made the animation part, but I need help after I drop the list item to the "Account To" list.
I'll attach a screenshot to make it clear.
As you see in the picture, I need to drag one item from the "From Account" list and drop it on only one item on "To Account" list. How can I achieve this ?
I've created a small sample which shows drag-drop between two ListViews filled with some Accounts. I will skip the implementation of UserControls - the Page xaml looks like this:
<Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
<Grid.RowDefinitions>
<RowDefinition Height="200"/>
<RowDefinition Height="200"/>
</Grid.RowDefinitions>
<ListView Header="Source" Margin="10" Grid.Row="0" CanDragItems="True" ItemsSource="{x:Bind Accounts}" SelectionMode="None">
<ListView.ItemsPanel>
<ItemsPanelTemplate>
<ItemsStackPanel Orientation="Horizontal"/>
</ItemsPanelTemplate>
</ListView.ItemsPanel>
<ListView.ItemTemplate>
<DataTemplate>
<controls:AccountControl CanDrag="True" DragStarting="AccountControl_DragStarting"/>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
<ListView Header="Targets" Margin="10" Grid.Row="1" ItemsSource="{x:Bind Accounts}" SelectionMode="None">
<ListView.ItemsPanel>
<ItemsPanelTemplate>
<ItemsStackPanel Orientation="Horizontal"/>
</ItemsPanelTemplate>
</ListView.ItemsPanel>
<ListView.ItemTemplate>
<DataTemplate>
<controls:AccountControl AllowDrop="True" DragEnter="AccountControl_DragEnter" Drop="AccountControl_Drop"/>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</Grid>
As you can see there is a Source list in which the control is firing an event when it's being dragged.
private void AccountControl_DragStarting(UIElement sender, DragStartingEventArgs args)
{
if ((sender as AccountControl)?.DataContext is Account account)
{
args.AllowedOperations = DataPackageOperation.Link;
args.Data.SetData(accountId, account.Id);
}
}
The Account class apart from name and balance has a Guid identifier so I can use it to pass information which source account has been used in transfer method.
The items in second list (Targets) accepts only drop operation and for this purpose fire two events:
private void AccountControl_DragEnter(object sender, DragEventArgs e)
{
e.AcceptedOperation = DataPackageOperation.Link;
e.DragUIOverride.Caption = "Transfer";
}
private async void AccountControl_Drop(object sender, DragEventArgs e)
{
if ((e.OriginalSource as AccountControl)?.DataContext is Account targetAccount)
if (await (e.DataView.GetDataAsync(accountId)) is Guid sourceAccountId)
{
var sourceAccount = Accounts.First(x => x.Id == sourceAccountId);
sourceAccount.Balance -= 1000;
targetAccount.Balance += 1000;
}
}
The first one sets accepted operation and some information for the user. The second one 'transfers' some money from one account to the second.
Everything looks like this:
Some more help you can find at MS directly, other article and in MS samples repository.
I am not fully satisfied with the "solutions" which I will provide. They are much likely very far away from the ideal implementations, but ...
The XAML code which I created to try to replicate as easily, but also consistently your object, consisted in a group of draggable Rectangles inside a StackPanel Control, plus another StackPanel Control where the items could be dragged into.
<Grid>
<Grid.Resources>
<Style TargetType="Rectangle">
<Setter Property="Width" Value="300"/>
<Setter Property="Height" Value="300"/>
<Setter Property="CanDrag" Value="True"/>
</Style>
</Grid.Resources>
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
<RowDefinition Height="*"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<StackPanel Name="StackPanelRectangles" Grid.Row="0" Orientation="Horizontal">
<Rectangle x:Name="RedRect" Fill="Red" DragStarting="Rectangle_DragStarting"/>
<Rectangle x:Name="GreenRect" Fill="Green" DragStarting="Rectangle_DragStarting"/>
<Rectangle x:Name="BlueRect" Fill="Blue" DragStarting="Rectangle_DragStarting"/>
</StackPanel>
<StackPanel Name="StackPanelDropArea" Background="Azure" AllowDrop="True"
DragOver="StackPanel_DragOver" Drop="StackPanel_Drop"
Grid.Row="2" Orientation="Horizontal"
HorizontalAlignment="Center">
<TextBlock>Drop anywhere in this area area</TextBlock>
</StackPanel>
</Grid>
1st Solution:
I routed every DragStarting event of the multiple Rectangles to the same EventHandler. In this EventHandler, we have access to the UIElement which is being dragged, so with an exposed property of type UIElement in your Page class, and you can simply clone the necessary properties for when you need to drop it, like this:
UIElement dragItem;
private void Rectangle_DragStarting(UIElement sender, DragStartingEventArgs args)
{
dataPackage.RequestedOperation = DataPackageOperation.Copy;
dragItem = sender;
}
Then when the item is dropped EventHandler is called, I have simply add it onto my DropArea.
private void StackPanel_Drop(object sender, DragEventArgs e)
{
Rectangle newElement = new Rectangle();
newElement.Width = (dragItem as Rectangle).Width;
newElement.Height = (dragItem as Rectangle).Height;
newElement.Fill = (dragItem as Rectangle).Fill;
StackPanelDropArea.Children.Add(newElement);
}
You cannot add your new Control by setting to reference the object being dragged, since there are properties such as the respective Parent which will thrown an exception when you try to add the Control to a different container.
2nd Solution:
I was extremely focused on on utilizing the DataPackage object, and one of its supported default formats, but I don't think any of them can actually hold data of an Object, such as our UIElement.
But each DataPackage instance supports a set of properties, which corresponds a Dictionary. We can set the Dictionary to hold UIElement in there, as long as we specify a key to reference that same object later on.
private void Rectangle_DragStarting(UIElement sender, DragStartingEventArgs args)
{
dataPackage.RequestedOperation = DataPackageOperation.Copy;
args.Data.Properties.Add("myRectangle", sender);
}
In the drop Event Handler, you can obtain the UIElement, like such:
private async void StackPanel_Drop(object sender, DragEventArgs e)
{
Rectangle element = e.DataView.Properties["myRectangle"] as Rectangle;
......
......
}
3rd Solution:
This solution used the method SetText(String) exposed by DataPackage, to hold the value of the Name property of the UIElement being dragged.
private void Rectangle_DragStarting(UIElement sender, DragStartingEventArgs args)
{
dataPackage = new DataPackage();
dataPackage.RequestedOperation = DataPackageOperation.Copy;
Rectangle rectangle = sender as Rectangle;
dataPackage.SetText(rectangle.Name);
Clipboard.SetContent(dataPackage);
}
By knowing the value of the Name property of the UIElement which is being dragged, looked for it, by using the VisualTreeHelper Class, like this:
private async void StackPanel_Drop(object sender, DragEventArgs e)
{
DataPackageView dataPackageView = Clipboard.GetContent();
if (dataPackageView.Contains(StandardDataFormats.Text))
{
draggedObject = await dataPackageView.GetTextAsync();
}
// Dragged objects come from another one of our Parent's Children
DependencyObject parent = VisualTreeHelper.GetParent(StackPanelDropArea);
int count = VisualTreeHelper.GetChildrenCount(parent);
for(int i=0; i< count; i++)
{
DependencyObject child = VisualTreeHelper.GetChild(parent, i);
if(child.GetType().Equals(typeof(StackPanel)))
{
StackPanel currentStackPanel = child as StackPanel;
if(currentStackPanel.Name == "StackPanelRectangles")
{
int numberOfRectangles = VisualTreeHelper.GetChildrenCount(currentStackPanel);
for(int j=0; j<numberOfRectangles; j++)
{
if(VisualTreeHelper.GetChild(currentStackPanel,j).GetType().Equals(typeof(Rectangle)))
{
Rectangle currentRectangle = VisualTreeHelper.GetChild(currentStackPanel, j) as Rectangle;
if (draggedObject != string.Empty && currentRectangle.Name.Equals(draggedObject))
{
Rectangle newRectangle = new Rectangle();
newRectangle.Width = currentRectangle.Width;
newRectangle.Height = currentRectangle.Height;
newRectangle.Fill = currentRectangle.Fill;
StackPanelDropArea.Children.Add(newRectangle);
}
}
}
}
}
} */
}
Result:
I usually try tackling this several times before giving up and using a third party library. The one I typically use is:
https://github.com/punker76/gong-wpf-dragdrop
You may subscribe to PointerPressed event in your DataTemplate and extract all the things you need.
XAML:
<DataTemplate x:Name="DataTemplate">
<Grid Background="Transparent" PointerPressed="Grid_OnPointerPressed"/>
</DataTemplate>
Code:
private void Grid_OnPointerPressed(object sender, PointerRoutedEventArgs e)
{
//your FrameworkElement
var frameworkElement = sender as FrameworkElement;
//global position of your element
var itemPosition = frameworkElement.TransformToVisual(Window.Current.Content).TransformPoint(new Point(0, 0)).ToVector2();
//your data
var selectedItemData = frameworkElement.DataContext as ItemData;
}
Save your data, use UWP Drag'n'Drop. On drop load your data.
I know this has been asked a few times but none of the answers work for me.
I've got this list of elements in a TreeView which when clicked show their parameters in a panel (with texboxes, labels, combos and other components). I also have a button that allows copying data from one element chosen in a combo to the one currently shown on the data panel ("copy data from...").
All the data in the panel is bound to a class and its properties, and the treeview is bound to a collection of those objects.
The problem is, when I press the copy button (i.e. copying data from object A to current object) the changes are not reflected. Yet, if I change the element to be displayed (clicking another object in the TreeView) then go back to the changed object, the changes are there. So it is in fact changing the actual data, but not refreshing de data bindings.
Strangely enough, If I press the Copy button twice, changes are indeed reflected.
Which may be the cause of this, and how would I solve it?
This is the XAML example of a control that should be updated (I only post one because there are lots, and none of them is working), the copy button and the treeview:
Copiar datos del protocolo:
Copiar
<TreeView SelectedItemChanged="TvProtocolosSelectedItemChanged" Margin="10,5" Name="tvProtocolos" HorizontalAlignment="Stretch" VerticalAlignment="Top" Width="200" Height="292" MinWidth="0">
<TreeView.ItemContainerStyle>
<Style TargetType="TreeViewItem">
<Setter Property="IsEnabled" Value="{Binding Path=Activo}"/>
<Setter Property="IsExpanded" Value="False"/>
</Style>
</TreeView.ItemContainerStyle>
<TreeView.ItemTemplate>
<HierarchicalDataTemplate ItemsSource="{Binding Path=Children}">
<StackPanel Orientation="Horizontal">
<Image Source="imagenes\file_icon.gif" Margin="0,0,5,0" />
<TextBlock Text="{Binding Path=Name}" ></TextBlock>
</StackPanel>
</HierarchicalDataTemplate>
</TreeView.ItemTemplate>
</TreeView>
And this is the code-behind file:
public partial class NewXP2 : Window
{
private const int nProtocolos = 100;
private Experiencia2 _exp2Class = new Experiencia2(nProtocolos);
private readonly TreeView _tvProtocolos;
public NewXP2()
{
InitializeComponent();
_tvProtocolos.ItemsSource = _expClass.Protocolos;
xpControlsPanel.DataContext = _exp2Class.GetProtocolo(0);
for(int i=0;i<nProtocolos;i++)
copyFromCombo.Items.Add("Protocolo " + (i+1));
copyFromCombo.SelectedIndex = 0;
}
private void CopyfromButtonClick(object sender, RoutedEventArgs e)
{
int protIndex = copyFromCombo.SelectedIndex;
int indiceProtocolo = 0;
if (_tvProtocolos == null)
return;
var g = _tvProtocolos.SelectedItem as Composite;
_listaData = GetData();
_tvProtocolos.ItemsSource = _listaData;
TreeViewItem tvi;
if (g != null)
{
tvi = _tvProtocolos.ItemContainerGenerator.ContainerFromIndex(g.Indice) as TreeViewItem;
if (tvi != null)
{
tvi.IsSelected = true;
}
indiceProtocolo = g.Indice;
}
_exp2Class.SetProtocolo(indiceProtocolo, _exp2Class.Protocolos[protIndex]);
}
And this is the class bound to the panel (with the textbox)
public class ProtocoloExp2: ISerializable, IDataErrorInfo
{
public ProtocoloExp2(int idx)
{
IndiceProtocolo = idx;
IndiceVisual = idx + 1;
}
public float TimeToShowTarget { get; set; }
(...)
}
Check "How to: Implement the INotifyPropertyChanged Interface" out. And consider using ObservableCollection for your lists. Implementation of INotifyPropertyChanged interface is a requirement for wpf controls view models it basically allows views to refresh themselves when data changes.