in the tutorial http://docs.oracle.com/javafx/2/ui_controls/tree-view.htm it explain how to create a TreeView with ContextMenu or CheckBox.
but is it possible to have both of them?
when I first copy-paste the code, I learned that I can have only one setCellFactory since they overwrite each other.
// the following two setCellFactory are copied from the tutorial
// this create TreeCell with ContextMenu
treeView.setCellFactory(new Callback<TreeView<String>,TreeCell<String>>(){
#Override
public TreeCell<String> call(TreeView<String> p) {
return new TextFieldTreeCellImpl();
//the class TextFieldTreeCellImp is a TreeCell with ContextMenu
}
});
// this create TreeCell with CheckBox
tree.setCellFactory(CheckBoxTreeCell.<String>forTreeView());
then i tried substituting TreeCell with CheckBoxTreeCell
//class TextFieldTreeCellImpl extends TreeCell<String> {
class TextFieldTreeCellImpl extends CheckBoxTreeCell<String> {
...
//TreeItem newTag = new TreeItem<String>("New tag");
CheckBoxTreeItem newTag = new CheckBoxTreeItem<String>("New tag");
but the checkbox didn't appear. it's still a normal treeview.
I think the fastest way to get a TreeView with CheckBoxes and a ContextMenu is adding the ContextMenu within the factory callback:
treeView.setCellFactory(new Callback<TreeView<String>,TreeCell<String>>() {
#Override
public TreeCell<String> call(TreeView<String> param) {
TreeCell<String> cell = CheckBoxTreeCell.<String>forTreeView().call(param);
ContextMenu menu = new ContextMenu();
MenuItem item1 = new MenuItem("Item 1");
MenuItem item2 = new MenuItem("Item 2");
EventHandler<ActionEvent> eh = new EventHandler<ActionEvent>() {
#Override
public void handle(ActionEvent event) {
System.out.println("Item '" + event.getSource() +
"' of cell '" + cell + "' was clicked.");
}
};
item1.setOnAction(eh);
item2.setOnAction(eh);
menu.getItems().add(item1);
menu.getItems().add(item2);
cell.setContextMenu(menu);
return cell;
}
});
I call the usual TreeCell factory for the CheckBoxes. However, before returning the cell I add the ContextMenu.
Using the EventHandler, you can specify the clicking behaviour of the respective item.
If you have further questions or this is not a satisfying solution of you problem, feel free to contact me.
Related
I am using vaadin 7.7.7
In a grid i have a combobox as an edited item in one of the columns
as
grid.addColumn("columnProperty").setEditorField(combobox);
I need to update a property/cell in same row based on the combobox selection change
My issue is , the selection change event triggers twice, once when the combobox in clicked and second when the selection value is changed. But the updated value in next cell gets reflected on UI only first time.
Below is the code written . Any solutions?
Combobox.addValueChangeListener(new ValueChangeListener()
#Override
public void valueChange(ValueChangeEvent event) {
// below line works only first time when the combobox is clicked,but i want
//it when the item in the combobox is changed
gridContainer.getContainerProperty(editedRow,"editedColumProperty").setValue("ValueTobeUpdated");}
});
Need to update the unit column on combobox change in edited mode(before saving)
Refer below link for image
example image
You will get value change events even when the field gets value that it should show to the user. In order to get event that indicates that the user has accepted the input you should use field group (setEditorFieldGroup).
From Book of Vaadin example for grid editing:
grid.getColumn("name").setEditorField(nameEditor);
FieldGroup fieldGroup = new FieldGroup();
grid.setEditorFieldGroup(fieldGroup);
fieldGroup.addCommitHandler(new CommitHandler() {
private static final long serialVersionUID = -8378742499490422335L;
#Override
public void preCommit(CommitEvent commitEvent)
throws CommitException {
}
#Override
public void postCommit(CommitEvent commitEvent)
throws CommitException {
Notification.show("Saved successfully");
}
});
Edit
I assume that you want to connect Parameter and Unit comboboxes. I would do that with this kind of value change lister
BeanItemContainer container = new BeanItemContainer<>(
Measurement.class,
measurements);
Grid grid = new Grid(container);
grid.setEditorEnabled(true);
ComboBox parameterComboBox = new ComboBox();
ComboBox unitComboBox = new ComboBox();
parameterComboBox.addItems(Parameter.Pressure, Parameter.Temperature, Parameter.Time);
parameterComboBox.addValueChangeListener(v -> setUnits(parameterComboBox, unitComboBox));
grid.getColumn("parameter").setEditorField(parameterComboBox);
grid.getColumn("unit").setEditorField(unitComboBox);
Units could be updated like this. I think you need to preserve current value and set it back if you replace available items in the combobox.
private void setUnits(ComboBox parameterComboBox, ComboBox unitComboBox) {
Object currentValue = unitComboBox.getValue();
List<String> units = unitsForParameter(parameterComboBox.getValue());
unitComboBox.removeAllItems();
unitComboBox.addItems(units);
if (units.contains(currentValue)) {
unitComboBox.setValue(currentValue);
} else {
unitComboBox.setValue(null);
}
}
private List<String> unitsForParameter(Object value) {
if (value == null) {
return Collections.emptyList();
} else if (value == Parameter.Pressure) {
return asList("Pascal", "Bar");
} else if (value == Parameter.Temperature) {
return asList("Celcius", "Kelvin");
} else if (value == Parameter.Time) {
return asList("Second", "Minute");
} else {
throw new IllegalArgumentException("Unhandled value: " + value);
}
}
TextBox has a default context menu. I would like to add an item to it. OK, that means cloning the default one, and adding an extra item to that.
I'd like to reuse some code here. I have five textboxes. Each needs the additional item on its context menu. The item needs act on the textbox that was clicked. I know "copy and paste" is the recommended method of code reuse in WPF, but if possible I'd prefer not to define five menus in XAML and five commands in the code behind.
Is there any reasonably clean and quick way to do this in WPF?
public partial class MyGhastlyView
{
/* blah blah */
private void MenuCut_Click(object sender, RoutedEventArgs e)
{
try
{
(sender as MenuItem).GetPlacementTarget<TextBox>().Cut();
}
catch (Exception)
{
}
}
/* blah blah */
}
public static class FurshlugginerExtensions
{
public static bool TryGetPlacementTarget<TTargetType>(this MenuItem mi,
out TTargetType target) where TTargetType : class
{
target = null;
var cm = mi.GetContextMenu();
if (null != cm)
{
target = cm.PlacementTarget as TTargetType;
}
return null != target;
}
public static TTargetType GetPlacementTarget<TTargetType>(this MenuItem mi)
where TTargetType : class
{
var cm = mi.GetContextMenu();
return (cm == null)
? null
: cm.PlacementTarget as TTargetType;
}
public static ContextMenu GetContextMenu(this MenuItem mi)
{
var logicalParent = LogicalTreeHelper.GetParent(mi);
if (logicalParent is ContextMenu)
{
return logicalParent as ContextMenu;
}
else if (logicalParent is MenuItem)
{
return (logicalParent as MenuItem).GetContextMenu();
}
return null;
}
}
UPDATE
What I'm looking for turns out to be a RoutedUICommand, with some futzing around in XAML. It knows what you clicked on (with some Kafkaesque exceptions due to event bubbling -- but can just set the CommandParameter on the ContextMenu).
Unfortunately, ContextMenuOpening event will not work here. For whatever reason, TextBox does not expose its context menu, and is always null unless you set it with your own. Perhaps it simply pops a private menu on right mouse click.
Charles Petzold speaks about that with RichTextBox here. (Both TextBox and RichTextBox derive from TextBoxBase, which appears to define that behavior)
It seems you will have to create your own, and duplicate the existing items.
Several articles demonstrate exactly this, like the one here.
Hope this helps.
EDIT:
However if you insist on editing the current menu, it appears someone has done so here (using an extension method and reflection).
After further investigation of the above attempt, it seems that the author is creating an instance of an EditorContextMenu (private class which derives from ContextMenu in System.Windows.Documents) and assigning it to the TextBox ContextMenu property, then adding the parameter menu items to the newly created menu. In effect, overriding the current menu. While you do get the original implementation, I am not sure I would favor this solution.
EDIT 2:
The following code will create only one instance of custom menu, bind Ctrl-D to the textboxes, along with the correlating ContextMenu item.
public static RoutedCommand ItemActionCommand = new RoutedCommand();
public MainWindow()
{
InitializeComponent();
CommandBinding commandBinding = new CommandBinding(ItemActionCommand, new ExecutedRoutedEventHandler(ItemActionCommandEventHandler));
KeyBinding keyBinding = new KeyBinding(ItemActionCommand, new KeyGesture(Key.D, ModifierKeys.Control));
MenuItem item = new MenuItem();
item.Click += CustomContextMenuItem_Click; // not really necessary
item.Header = "Custom Menu Item";
item.InputGestureText = "Ctrl+D";
item.Command = ItemActionCommand;
ContextMenu menu = new ContextMenu();
menu.Items.Add(item);
Grid container = new Grid();
this.Content = container;
for (int i = 0; i < 5; i++)
container.Children.Add(this.CreateTextBox("Value: " + i.ToString(), (i + 1) * 30.0d, menu, commandBinding, keyBinding));
}
private void ItemActionCommandEventHandler(object sender, ExecutedRoutedEventArgs e)
{
TextBox textBox = e.Source as TextBox;
Debug.Assert(textBox != null);
// perform actions against textbox here
}
private void CustomContextMenuItem_Click(object sender, RoutedEventArgs e)
{
MenuItem item = sender as MenuItem;
Debug.Assert(item != null);
TextBox textBox = ((ContextMenu)item.Parent).PlacementTarget as TextBox;
Debug.Assert(textBox != null);
// no need to do anything here since the command handler above will fire
// but for the sake of completeness
}
private TextBox CreateTextBox(string text, double topOffset, ContextMenu menu, CommandBinding commandBinding, KeyBinding keyBinding)
{
TextBox textbox = new TextBox();
textbox.HorizontalAlignment = HorizontalAlignment.Center;
textbox.VerticalAlignment = VerticalAlignment.Top;
textbox.Margin = new Thickness(0.0d, topOffset, 0.0d, 0.0d);
textbox.CommandBindings.Add(commandBinding);
textbox.InputBindings.Add(keyBinding);
textbox.ContextMenu = menu;
textbox.Width = 150.0d;
textbox.Height = 25.0d;
textbox.Text = text;
return textbox;
}
Screenshot:
It is possible with an AttachedProperty and the handling of the ContextMenuOpening event. Look here and here. Should take around 100 lines of code and one line in xaml.
For completenes sake:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
namespace WpfApplication1
{
public class CustomMenuAction
{
public static bool GetHasMenuItemAction(DependencyObject obj)
{
return (bool)obj.GetValue(HasMenuItemActionProperty);
}
public static void SetHasMenuItemAction(DependencyObject obj, bool value)
{
obj.SetValue(HasMenuItemActionProperty, value);
}
// Using a DependencyProperty as the backing store for MyProperty. This enables animation, styling, binding, etc...
public static readonly DependencyProperty HasMenuItemActionProperty =
DependencyProperty.RegisterAttached("HasMenuItemAction", typeof(bool), typeof(CustomMenuAction), new PropertyMetadata(default(bool),OnPropertyChanged));
private static void OnPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
if((bool)e.NewValue)
{
var textbox = d as TextBox;
if(textbox != null)
{
textbox.ContextMenu = GetCustomContextMenu();
textbox.ContextMenuOpening += textbox_ContextMenuOpening;
}
}
}
private static ContextMenu GetCustomContextMenu()
{
var contextMenu = new ContextMenu();
var standardCommands = GetStandardCommands();
foreach (var item in standardCommands)
{
contextMenu.Items.Add(item);
}
return contextMenu;
}
private static IList<MenuItem> GetStandardCommands()
{
//From https://stackoverflow.com/a/210981/3411327
List<MenuItem> standardCommands = new List<MenuItem>();
MenuItem item = new MenuItem();
item.Command = ApplicationCommands.Cut;
standardCommands.Add(item);
item = new MenuItem();
item.Command = ApplicationCommands.Copy;
standardCommands.Add(item);
item = new MenuItem();
item.Command = ApplicationCommands.Paste;
standardCommands.Add(item);
return standardCommands;
}
static void textbox_ContextMenuOpening(object sender, ContextMenuEventArgs e)
{
//From MSDN example: http://msdn.microsoft.com/en-us/library/bb613568.aspx
var textbox = e.Source as TextBox;
ContextMenu cm = textbox.ContextMenu;
foreach (MenuItem mi in cm.Items)
{
if ((String)mi.Header == "Item4") return;
}
MenuItem mi4 = new MenuItem();
mi4.Header = "Item4";
mi4.Click += (o, args) =>
{
var menuItem = o as MenuItem;
MessageBox.Show(menuItem.Header.ToString(), textbox.Text);
};
textbox.ContextMenu.Items.Add(mi4);
}
}
}
<TextBox namespace:CustomMenuAction.HasMenuItemAction="True"></TextBox>
I have Accordion which is bound to ObservableCollection. I need to apply workaround to make Accordion resize its children to the content (ie if an item has been deleted from the bound collection I need the accordion to shrink, and if added - to expand).
However all the workaround I found use AccordionItem objects. They all have AccordionItem items set in XAML so their accordion.Items are collections of AccordionItem objects.
Although I am binding to myObject they are placed in AccordionItem object in the ItemContainerStyleTemplate. The only thing I need is to access that AccordionItem somehow. If I try something like accordion.Items[0].GetType() it returns myObject.
So the question is - how do I access AccordionItem object from data bound Accordion?
The workaround I wanted to try: (EDIT: It does work as I needed)
public static void UpdateSize(this AccordionItem item)
{
item.Dispatcher.BeginInvoke(
delegate
{
if (!item.IsLocked && item.IsSelected)
{
item.IsSelected = false;
item.InvokeOnLayoutUpdated(delegate { item.IsSelected = true; });
}
});
}
I've had to do similar things to Accordions, and the only way I was able to get down to the AccordionItems was by walking the visual tree.
Here's how I did it: Given these extension methods :
public static IEnumerable<DependencyObject> GetAllChildrenOfType(this DependencyObject depObject, Type t, bool recursive = true)
{
List<DependencyObject> objList = new List<DependencyObject>();
var childrenList = depObject.GetChildren();
foreach (DependencyObject i in childrenList)
{
Type ct = i.GetType();
if (ct == t)
objList.Add(i);
if (recursive)
objList.AddRange(i.GetAllChildrenOfType(t));
}
return objList.ToArray();
}
public static IEnumerable<DependencyObject> GetChildren(this DependencyObject depObject)
{
int count = depObject.GetChildrenCount();
for (int i = 0; i < count; i++)
{
yield return VisualTreeHelper.GetChild(depObject, i);
}
}
Now you can get all the AccordionItems in a given Accordion:
var accordionItemList = myAccordion.GetAllChildrenOfType(typeof(AccordionItem));
foreach (AccordionItem i in accordionItemList)
{...}
This may be a bit more complicated than needed, in my instance I had an accordion within the accordion, which made things difficult in the end.
Silverlight 4 now include the option for creating a context menu upon right clicking. Can anyone provide me with an example of a treeview with a right click context menu for the treeview?
Ultimately I want a the menu to show different options depending upon the node depth selected - bonus points if the example includes this!
You can use this open source menu for this:
http://sl4popupmenu.codeplex.com
The control supports right click on TreeViews out of the box. The code has been adapted from the sample code on the homepage to use a TreeView instead of a DataGrid:
private void GenerateMenu()
{
var data = new ObservableCollection<string>("Item 1,Item 2,Item 3,Item 4,Item 6,Item 7,Item 8".Split(','));
TreeView treeView1 = new TreeView() { Margin = new Thickness(50), ItemsSource = data };
this.LayoutRoot.Children.Add(dataGrid1);
// Create the submenu
var pmTimeSub = new PopupMenu();
pmTimeSub.AddItem("Time Now", null);
// Create the main menu
var pm = new PopupMenu();
pm.AddItem("Delete row", delegate { data.RemoveAt(dataGrid1.SelectedIndex); });
pm.AddSeparator();
pm.AddSubMenu(pmTimeSub, "Get Time ", "images/arrow.png", null, null, false, null);
// Attach the submenu pmTimeSub
pm.AddSeparator();
pm.AddItem("Demo2", delegate { this.Content = new Demo2(); });
// Set dataGrid1 as the trigger element
pm.AddTrigger(TriggerTypes.RightClick, treeView1);
// Showing main menu
pm.Showing += (sender, e) =>
{
pm.PopupMenuItem(0).Header = "Delete " + treeView1.SelectedItem;
TreeViewItem tvi = pm.GetClickedElement<TreeViewItem>();
// Add code to calculate the node depth here using the GetParentTreeViewItem method
// Add code to modify the menu items according to the node depth value.
pm.PopupMenuItem(0).IsVisible =
pm.PopupMenuItem(1).IsVisible = tvi != null;
};
// Showing submenu
pmTimeSub.Showing += delegate
{
pmTimeSub.PopupMenuItem(0).Header = DateTime.Now.ToLongTimeString();
};
}
Note that the code does not allow you to show different menus upon the node depth yet. To do this you can use the following method to get the parent of the TreeViewItem that was clicked:
private static TreeViewItem GetParentTreeViewItem(DependencyObject item)
{
if (item != null)
{
DependencyObject parent = VisualTreeHelper.GetParent(item);
TreeViewItem parentTreeViewItem = parent as TreeViewItem;
return parentTreeViewItem ?? GetParentTreeViewItem(parent);
}
return null;
}
From there you can determine depth of the node by calling the GetParentTreeViewItem function in a loop until the parent is null. You would place this code in the event where the menu is being shown and then add the necessary code in there to show the appropriate menu.
Hope this helps.
So, I tried the above code, downloaded and attempted to include within my Existing Silverlight Application. I was able to find an easier solution. This will add a Context Menu allowing Right-Clicks on the Branches (Headers, or Parent Nodes).
private ContextMenu menu;
foreach(var model in models)
{
// Populate the Tree View Control
var cb = new CheckBox {Content = model.Value};
cb.Click += new RoutedEventHandler(cb_Click);
var header = new TreeViewItem {Header = cb};
// Menu for Header
menu = new ContextMenu();
MenuItem setAsRows = new MenuItem();
setAsRows.Header = "Set as Rows";
setAsRows.Click += new RoutedEventHandler(setAsRows_Click);
menu.Items.Add(setAsRows);
MenuItem addToRows = new MenuItem();
addToRows.Header = "Add to Rows";
addToRows.Click += new RoutedEventHandler(addToRows_Click);
menu.Items.Add(addToRows);
MenuItem setAsCols = new MenuItem();
setAsCols.Header = "Set as Columns";
menu.Items.Add(setAsCols);
MenuItem addToCols = new MenuItem();
addToCols.Header = "Add to Columns";
menu.Items.Add(addToCols);
header.ContextMenu = menu;
treeView1.Items.Add(header);
var thisItem = treeView1.Items;
// Model Contexts
var contexts = myFramework.GetConceptsOfModel(model.Key);
// Add Leafs To Branch
foreach(var context in contexts)
{
cb = new CheckBox {Content = context.Value.ToString()};
header.Items.Add(cb);
}
}
The datatemplate for the ListBox is set dynamically by XamlReader.Load. I am subscribing to Checked event by getting the CheckBox object using VisualTreeHelper.GetChild. This event is not getting fired
Code Snippet
public void SetListBox()
{
lstBox.ItemTemplate =
XamlReader.Load(#"<DataTemplate xmlns='http://schemas.microsoft.com/winfx/2006/xaml/presentation' xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml' x:Name=""DropDownTemplate""><Grid x:Name='RootElement'><CheckBox x:Name='ChkList' Content='{Binding " + TextContent + "}' IsChecked='{Binding " + BindValue + ", Mode=TwoWay}'/></Grid></DataTemplate>") as DataTemplate;
CheckBox chkList = (CheckBox)GetChildObject((DependencyObject)_lstBox.ItemTemplate.LoadContent(), "ChkList");
chkList.Checked += delegate { SetSelectedItemText(); };
}
public CheckBox GetChildObject(DependencyObject obj, string name)
{
for (int i = 0; i < VisualTreeHelper.GetChildrenCount(obj); i++)
{
DependencyObject c = VisualTreeHelper.GetChild(obj, i);
if (c.GetType().Equals(typeof(CheckBox)) && (String.IsNullOrEmpty(name) || ((FrameworkElement)c).Name == name))
{
return (CheckBox)c;
}
DependencyObject gc = GetChildObject(c, name);
if (gc != null)
return (CheckBox)gc;
}
return null;
}
How to handle the checked event? Please help
You need to understand the reason why ItemTemplate is a DataTemplate. For each item the the list box needs to display it will call the LoadContent() method. This create a new instance of the content described including, in this case, a new checkbox. All this then gets bound to the item when it is assigned as the Content of a ListBoxItem.
All the instances of checkbox in this case are independent objects. All you have done is created yet another independent instance which is not used anywhere in the actual UI and attached an event handler to it. None of the checkboxes for the items in the list will share this handler hence the event code is never called.
Removed ItemTemplate and added the below code
var checkBox = new CheckBox { DataContext = item };
if (string.IsNullOrEmpty(TextContent)) checkBox.Content = item.ToString();
else
checkBox.SetBinding(ContentControl.ContentProperty,
new Binding(TextContent) { Mode = BindingMode.OneWay });
if (!string.IsNullOrEmpty(BindValue))
checkBox.SetBinding(ToggleButton.IsCheckedProperty,
new Binding(BindValue) { Mode = BindingMode.TwoWay });
checkBox.SetBinding(IsEnabledProperty, new Binding("IsEnabled") { Mode = BindingMode.OneWay });
checkBox.Checked += (sender, RoutedEventArgs) => { SetSelectedItemText(true, ((CheckBox)sender).GetValue(CheckBox.ContentProperty).ToString()); };
checkBox.Unchecked += (sender, RoutedEventArgs) => { SetSelectedItemText(true, ((CheckBox)sender).GetValue(CheckBox.ContentProperty).ToString()); };
This fixed the issue