I have a Treeview which is doing lazy loading. I used MVVM. I wanted to select the top node of the tree by default when my application launches.
I think there is a better way... Just ceate a class that inherits from System.Windows.Controls.TreeView and override OnItemsChanged(System.Collections.Specialized.NotifyCollectionChangedEventArgs e). And in this method put this code:
if (base.SelectedItem == null)
{
if(base.Items.Count != 0)
{
(base.ItemContainerGenerator.ContainerFromItem(base.Items[0]) as TreeViewItem).IsSelected = true;
}
}
base.OnItemsChanged(e);
And that's it.
The easiest way to do this is use a style with an IsSelected property:
<Style x:Key="SelectableTreeViewItem" TargetType="TreeViewItem">
<Setter Property="IsSelected" Value="{Binding IsSelected, Mode=TwoWay}" />
</Style>
Then expose this property in your model, or more specifically in the object that you bind to for your top level node.
public class MyTopLevelFoo
{
public bool IsSelected { get; set; }
}
...and set it to true when you initially load:
IsSelected = true;
Just use Loaded Event
private void tvComponents_Loaded(object sender, RoutedEventArgs e)
{
(tvComponents.ItemContainerGenerator.ContainerFromIndex(0) as TreeViewItem).IsSelected = true;
}
Related
I have a WPF DataGridCheckBoxColumn, which is bound to an object that implements INotifyPropertyChanged as shown below:
DataGridCheckBoxColumn Binding="{Binding Path=IsSelected}" CellStyle="{StaticResource MyDataGridCheckBoxCellStyle}"/>
Here is the associated object:
public class ListItem : INotifyPropertyChanged
{
public int ID { get; set; }
private bool isSelected = false;
public bool IsSelected { get { return isSelected; } set { isSelected = value; OnChanged("IsSelected"); } }
public event PropertyChangedEventHandler PropertyChanged;
private void OnChanged(string prop)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(prop));
}
}
Everything works as expected, except that in order to set the Checkbox to Checked, I need to double click, in order to first select the column, and then set the checkbox value.
So, I decide to implement a Style trigger as shown below:
<Style x:Key="MyDataGridCheckBoxCellStyle" TargetType="DataGridCell">
<Style.Triggers>
<Trigger Property="IsMouseOver" Value="True" >
<Setter Property="IsEditing" Value="True" />
</Trigger>
</Style.Triggers>
</Style>
Now I am able to single click to Check the Checkbox, but my binding doesn't work anymore. Any idea of what is going on here? Why does setting the Style Trigger remove the binding?
Changing the style can cause some issues with the default template. You would most likely need to copy the entire style + template and then modify that to suit your needs. You could try this http://wpf.codeplex.com/wikipage?title=Single-Click%20Editing however.
In my application I'm using Prism and MahApps.metro. I created a RegionAdapter for FlyoutsControl, this is working like a charm.
The only problem is, that when I first navigate the View into Flyout Region, the Flyout pops up instead of sliding in from the side.
I can imagine this is because it is created at runtime and added to the FlyoutsControl at runtime, but is there a possibility to create the Flyout, add it to the FlyoutsControl and then show it via Slide-In effect?
Just setting the IsOpen property to false and then to open doesn't work :(
I was trying to do the same thing and was having issues but later i figured out the solution.
First, define the region name FlyoutRegion on FlyoutsControl.
<mahApps:MetroWindow ...>
<mahApps:MetroWindow.Flyouts>
<mahApps:FlyoutsControl prism:RegionManager.RegionName="FlyoutRegion">
<mahApps:FlyoutsControl.ItemContainerStyle>
<Style TargetType="{x:Type mahApps:Flyout}">
<Setter Property="Header" Value="{Binding Header}" />
<Setter Property="IsOpen" Value="{Binding IsOpen}" />
<Setter Property="Position" Value="{Binding Position}" />
</Style>
</mahApps:FlyoutsControl.ItemContainerStyle>
</mahApps:FlyoutsControl>
</mahApps:MetroWindow.Flyouts>
</mahApps:MetroWindow>
Create the RegionAdapter and register it in the Bootstrapper.
[Export]
public class FlyoutsControlRegionAdapter : RegionAdapterBase<FlyoutsControl>
{
[ImportingConstructor]
public FlyoutsControlRegionAdapter(IRegionBehaviorFactory factory)
: base(factory)
{
}
protected override void Adapt(IRegion region, FlyoutsControl regionTarget)
{
region.ActiveViews.CollectionChanged += (s, e) =>
{
if (e.Action == NotifyCollectionChangedAction.Add)
{
foreach (FrameworkElement element in e.NewItems)
{
Flyout flyout = new Flyout();
flyout.Content = element;
flyout.DataContext = element.DataContext;
regionTarget.Items.Add(flyout);
}
}
};
}
protected override IRegion CreateRegion()
{
return new AllActiveRegion();
}
}
Inside Bootstrapper
protected override RegionAdapterMappings ConfigureRegionAdapterMappings()
{
var mappings = base.ConfigureRegionAdapterMappings();
mappings.RegisterMapping(typeof(FlyoutsControl), Container.GetExportedValue<FlyoutsControlRegionAdapter>());
return mappings;
}
Finally, register the desired View with FlyoutRegion.
regionManager.RegisterViewWithRegion("FlyoutRegion", typeof(FlyoutView));
The trick here is to expose the Header, IsOpen and Position properties in the ViewModel and associate it with FlyoutView.
You can refer the detail on this Code Project Link
I wrote custom TreeView control.
XAML:
<TreeView x:Class="EArchiveMaster.View.MyTreeView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300">
<TreeView.ItemContainerStyle>
<Style TargetType="{x:Type TreeViewItem}">
<Setter Property="IsExpanded" Value="{Binding IsExpanded, Mode=TwoWay}" />
<Setter Property="IsSelected" Value="{Binding IsSelected, Mode=TwoWay}" />
<EventSetter Event="LostFocus" Handler="EventSetter_OnHandler" />
</Style>
</TreeView.ItemContainerStyle>
</TreeView>
.cs
public partial class MyTreeView
{
public event Action SomeItemLostFocus;
public MyTreeView()
{
InitializeComponent();
}
private void EventSetter_OnHandler(object sender, RoutedEventArgs e)
{
e.Handled = true;
if (SomeItemLostFocus != null)
SomeItemLostFocus();
}
}
But when I try to use it I got well known error:
Cannot set Name attribute value 'TextBox' on element 'TextBox'. 'TextBox' is under the scope of element 'MyTreeView', which already had a name registered when it was defined in another scope.
I found some receipts how to fix this error. Namely, specify .xaml part of control in its code-behind.
But I have no idea how can I do this.
The code clearly shows you want to extend TreeView. Basically if you want to build control that can hold some content(which can be named...), like ContentControl, ItemsControl, etc.. it is always better to go with CustomControl. UserControl with XAML and CS code is not suitable for this case.
In your case, create a class like below and extend the functionalities,
public class MyTreeView : TreeView
{
public event Action SomeItemLostFocus;
public MyTreeView()
{
DefaultStyleKey = typeof(MyTreeView);
}
public override void OnLostFocus(object sender, RoutedEventArgs e)
{
e.Handled = true;
if (SomeItemLostFocus != null)
SomeItemLostFocus();
}
}
If you want to customize the look and feel, you should override the default Style of the control. This style should be available in generic.xaml file inside Themes folder. More information on Custom Control development is here.
<Style TargetType="{x:Type TreeViewItem}">
<Setter Property="IsExpanded"
Value="{Binding IsExpanded, Mode=TwoWay}" />
<Setter Property="IsSelected"
Value="{Binding IsSelected, Mode=TwoWay}" />
</Style>
I found solution appropriate for me. This it the way how to define Style of TreeViewItem in code, not in XAML. Now I have TreeView definded only in code-behind, therefore, error will not be risen.
public class MyTreeView : TreeView
{
public event RoutedEventHandler ItemLostLogicFocus;
protected override void OnInitialized(EventArgs e)
{
base.OnInitialized(e);
var itemContainerStyle = new Style
{
TargetType = typeof(TreeViewItem),
};
#region Binding
var expandedBinding = new Binding("IsExpanded")
{
Mode = BindingMode.TwoWay,
};
var selectedBinding = new Binding("IsSelected")
{
Mode = BindingMode.TwoWay,
};
#endregion
#region Setters
itemContainerStyle.Setters.Add(new Setter
{
Property = TreeViewItem.IsExpandedProperty,
Value = expandedBinding
});
itemContainerStyle.Setters.Add(new Setter
{
Property = TreeViewItem.IsSelectedProperty,
Value = selectedBinding
});
#endregion
#region EventSetters
itemContainerStyle.Setters.Add(new EventSetter
{
Event = LostFocusEvent,
Handler = new RoutedEventHandler(ItemLostLogicFocusHandler)
});
#endregion
ItemContainerStyle = itemContainerStyle;
}
private void ItemLostLogicFocusHandler(Object sender, RoutedEventArgs e)
{
e.Handled = true;
if (ItemLostLogicFocus != null)
ItemLostLogicFocus(sender, e);
}
}
Variations of this question seems to be quite common, but I have yet to find a solution that works for me. I am attempting to place a Dropdown menu within a Button.ContextMenu, using an Observable collection, and thought I was on the right track, with one piece missing: I have yet to be able to get the index of the item selected, and although I can see my collection in the debugger, I am beginning to wonder if the items are really getting found, will explain as I go. First, the XAML...you can see that I have a binding for the Button Content, and the idea is that after a menu item gets selected, my code behind will update that property. Which it could, if I could get the index of the collection that is being collected:
<Button x:Name="DeviceSelMenuButton" Content="{Binding DeviceID_and_SN, Mode=TwoWay}" HorizontalAlignment="Left" Height="28" Margin="25,103,0,0" VerticalAlignment="Top" Width="187" FontSize="14" Click="DeviceSelMenuButton_Click">
<Button.ContextMenu>
<ContextMenu ItemsSource="{Binding DeviceID_SN_Collection, Mode=TwoWay}">
<ContextMenu.ItemContainerStyle>
<Style TargetType="MenuItem">
<Setter Property="IsCheckable" Value="true"/>
<Setter Property="Command" Value="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type Button}}, Path=DataContext.MyCommand}"/>
<Setter Property="CommandParameter" Value="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type ContextMenu}}}"/>
</Style>
</ContextMenu.ItemContainerStyle>
</ContextMenu>
</Button.ContextMenu>
</Button>
As you can imagine, I have tried many variations for the CommandParameter bindings, but with this one I can at least see some things in my ICommand method. The thing that troubles me is that at the first push of the button (and no there are no errors in the Output window regarding the bindings), under the ContextMenu cm's Items property, I see a legitimate items label under items.CurrentItem, and item.CurrentPosition is 0 -- which at first looked promising, was hoping I could use that as the index, until I realized I was looking at the last item and so it must be meaningless. After that, the second time, and all subsequent pushes of the button, the items.CurrentItem is null, and the items.Current position is 0xffffffff. Pasting in the relevant pieces of code, starting with the class defining the collection, ICommand, etc:
class CustomDeviceGUI : INotifyPropertyChanged
{
// Declare the event
public event PropertyChangedEventHandler PropertyChanged = delegate { };
private string _deviceDisplayString;
private ICommand UpdateMenuICommand;
List<string> ControllerDeviceList = new List<string>();
private System.Collections.ObjectModel.ObservableCollection<string> _DeviceID_SN_Collection = new System.Collections.ObjectModel.ObservableCollection<string>();
// CTOR
public CustomDeviceGUI()
{
ControllerDeviceList.Add("CustomDevice Device 1");
ControllerDeviceList.Add("CustomDevice Device 2");
ControllerDeviceList.Add("CustomDevice Device 3");
ControllerDeviceList.Add("CustomDevice Device 6");
UpdateDeviceID(3); // TODO Get from GUI!!!
}
#region CustomDeviceGUI Properties
public System.Collections.ObjectModel.ObservableCollection<string> DeviceID_SN_Collection
{
get
{
_DeviceID_SN_Collection.Clear();
foreach (string str in ControllerDeviceList)
{
_DeviceID_SN_Collection.Add(str);
}
return _DeviceID_SN_Collection;
}
private set
{
_DeviceID_SN_Collection = value;
}
}
public string DeviceID_and_SN
{
get
{
return _deviceDisplayString;
}
private set
{
_deviceDisplayString = value;
}
}
public ICommand MyCommand
{
get
{
if (UpdateMenuICommand == null)
UpdateMenuICommand = new MyGuiCommand();
return UpdateMenuICommand;
}
set
{
UpdateMenuICommand = value;
RaisePropertyChangeEvent("MyCommand"); // ????
}
}
public void UpdateDeviceID(int deviceID)
{
this._deviceDisplayString = ControllerDeviceList[deviceID];
RaisePropertyChangeEvent("DeviceID_and_SN");
RaisePropertyChangeEvent("DeviceID_SN_Collection");
}
public class MyGuiCommand : ICommand
{
// Two events are kicked off when the command is executed
public static event UpdateDeviceSelectedEventHandler UpdateDeviceSelectedEvent;
// defining signature for any event handlers for the events we create here
public delegate void UpdateDeviceSelectedEventHandler(int deviceIndex);
public void Execute(object parameter)
{
ContextMenu cm = (ContextMenu)parameter;
var itemSource = cm.ItemsSource;
var itemBG = cm.ItemBindingGroup;
var items = cm.Items;
UpdateDeviceSelectedEvent(1); // TODO parameter with index from GUI
}
public bool CanExecute(object parameter)
{
return true;
}
public event EventHandler CanExecuteChanged // was ;
{
add { CommandManager.RequerySuggested += value; }
remove { CommandManager.RequerySuggested -= value; }
}
}
} // class CustomDeviceGUI
and finally the relevant code from MainWindow.xaml.cs, not a lot happening here:
// register for the event from the ICommand.Execute
WpfBindingAttempts.CustomDeviceGUI.MyGuiCommand.UpdateDeviceSelectedEvent += new WpfBindingAttempts.CustomDeviceGUI.MyGuiCommand.UpdateDeviceSelectedEventHandler(UpdateDeviceSelectedAfterSwitch);
// Handles event that occurs when a different device is selected
// via the dropdown menu -- sets the active device, and updates its ID/SN
void UpdateDeviceSelectedAfterSwitch(int deviceIndex)
{
_customDeviceGui.UpdateDeviceID(deviceIndex);
}
code behind for button:
private void DeviceSelMenuButton_Click(object sender, RoutedEventArgs e)
{
// " (sender as Button)" is PlacementTarget
(sender as Button).ContextMenu.IsEnabled = true;
(sender as Button).ContextMenu.PlacementTarget = (sender as Button);
(sender as Button).ContextMenu.Placement = System.Windows.Controls.Primitives.PlacementMode.Bottom;
(sender as Button).ContextMenu.IsOpen = true;
}
Think that's everything. Any help is greatly appreciated. If this looks familiar, it is. I got over the first hurdles and slammed into this brick wall.
I don't think there's any direct way to return the selected index of an item within a ContextMenu. I don't believe the CurrentItem property holds anything to do with which item has been selected. I'm not sure exactly what this property does do (it might be something used internally by the framework), but I would recommend that you ignore it.
Instead of making the context menu be the CommandParameter, make the items in your collection the CommandParameters. To do this, change the CommandParameter setter to the following:
<Setter Property="CommandParameter" Value="{Binding}" />
Then, pass the list of all devices to your MyGuiCommand, in a constructor argument for example. Finally, in your Execute method, search within this list of devices to find the selected device, which will be in the parameter.
OK, a definite newbie here with WPF, and obviously need to keep learning more about MVVM, my code wasn't specifically designed that way, but I did designate one class to be the interface and controller for the GUI, whereas the model code resides in another set of classes. Have been scouring the web for examples, and questions similar to mine, of which there are plenty, but after three days of running through the maze I'm asking for help.
What I need is a simple dropdown menu, with items that can be dynamically updated (its an app that talks to a USB device, so however many are available should show up along with their device ID and serial number), and the currently selected item should show up on the Button (or whatever implementation of Dropdown menu I end up with). In this example, I just create a static list but that same list would be dynamically updated later on in the full app.
What I have so far looks like it is on the right track: I get the currently selected device id string to show up on the Button, and on pushing the Button, I get the list of all available devices (it doesn't bother me much that the currently selected device shows up redundantly in the list). However, I am not able to hook into any event when an item is selected, and thus can't update the item in the button, or do anything else for that matter.
My XAML below. Note that this was roughly hacked together, and there are some things in here that make no sense, like "IsActive" for the "IsChecked" property, that came from examples. The big problem is that as far as I can tell, none of the Setter properties in the ContextMenu.Resources seem to be doing anything at all...tried changing the fontsize to no avail. And the really big problem, of course, is that the "MyCommand" binding isn't working, that method never gets called.
<Label Content="Device Selected:" HorizontalAlignment="Left" Margin="25,22,0,0" VerticalAlignment="Top" Width="124" FontWeight="Bold" FontSize="14" Height="25"/>
<Button x:Name="DeviceSelMenuButton" Content="{Binding DeviceID_and_SN, Mode=TwoWay}" HorizontalAlignment="Left" Height="28" Margin="25,52,0,0" VerticalAlignment="Top" Width="187" FontSize="14" Click="DeviceSelMenuButton_Click">
<Button.ContextMenu>
<ContextMenu ItemsSource="{Binding DeviceID_SN_Collection, Mode=TwoWay}">
<ContextMenu.Resources>
<Style x:Key="SelectDeviceStyle" TargetType="MenuItem">
<Setter Property="Command" Value="{Binding MyCommand}"/>
<Setter Property="CommandTarget" Value="{Binding RelativeSource Self}"/>
<Setter Property="IsChecked" Value="{Binding IsActive}"/>
<Setter Property="IsCheckable" Value="True"/>
<Setter Property="FontSize" Value="14"/>
</Style>
</ContextMenu.Resources>
</ContextMenu>
</Button.ContextMenu>
</Button>
And the code from MainWindow.xaml.cs:
public partial class MainWindow : Window
{
CustomDeviceGUI _customDeviceGui = new CustomDeviceGUI();
public MainWindow()
{
InitializeComponent();
this.DataContext = _customDeviceGui;
}
private void DeviceSelMenuButton_Click(object sender, RoutedEventArgs e)
{
// " (sender as Button)" is PlacementTarget
(sender as Button).ContextMenu.IsEnabled = true;
(sender as Button).ContextMenu.PlacementTarget = (sender as Button);
(sender as Button).ContextMenu.Placement = System.Windows.Controls.Primitives.PlacementMode.Bottom;
(sender as Button).ContextMenu.IsOpen = true;
}
private void SomeMethod(object sender, DataTransferEventArgs e)
{
// TODO Somehow get the index of the selected menu item (collection index, 0-based)
// int selIndex = (sender as Button).ContextMenu.Items.IndexOf ??
_customDeviceGui.UpdateDeviceID("RelayPro id updated");
}
}
And the GUI code:
class CustomDeviceGUI : INotifyPropertyChanged
{
// Declare the event
public event PropertyChangedEventHandler PropertyChanged = delegate { };
private string _deviceDisplayString;
private ICommand _updateMenu;
List<string> ControllerDeviceList = new List<string>();
private System.Collections.ObjectModel.ObservableCollection<string> _DeviceID_SN_Collection = new System.Collections.ObjectModel.ObservableCollection<string>();
// CTOR
public CustomDeviceGUI()
{
ControllerDeviceList.Add("CustomDevice Device 1");
ControllerDeviceList.Add("CustomDevice Device 2");
ControllerDeviceList.Add("CustomDevice Device 3");
ControllerDeviceList.Add("CustomDevice Device 6");
UpdateDeviceID(ControllerDeviceList[0]);
}
#region CustomDeviceGUI Properties
public System.Collections.ObjectModel.ObservableCollection<string> DeviceID_SN_Collection
{
get
{
_DeviceID_SN_Collection.Clear();
foreach (string str in ControllerDeviceList)
{
_DeviceID_SN_Collection.Add(str);
}
return _DeviceID_SN_Collection;
}
private set
{
_DeviceID_SN_Collection = value;
}
}
public string DeviceID_and_SN
{
get
{
return _deviceDisplayString;
}
private set
{
_deviceDisplayString = value;
}
}
public ICommand MyCommand
{
get
{
if (_updateMenu == null)
_updateMenu = new MyGuiCommand();
return _updateMenu;
}
}
#endregion
#region Public Methods
public void UpdateDeviceID(string deviceID)
{
this._deviceDisplayString = deviceID;
RaisePropertyChangeEvent("DeviceID_and_SN");
RaisePropertyChangeEvent("DeviceID_SN_Collection");
}
#endregion
protected void RaisePropertyChangeEvent(string name)
{
PropertyChangedEventHandler handler = this.PropertyChanged;
try
{
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(name));
}
}
catch (Exception e)
{
// ... TODO Remove this catchall or find specific exceptions
}
}
public class MyGuiCommand : ICommand
{
public void Execute(object parameter)
{
// Debug.WriteLine("Hello, world");
int hmm = 3;
}
public bool CanExecute(object parameter)
{
return true;
}
public event EventHandler CanExecuteChanged // was ;
{
add { CommandManager.RequerySuggested += value; }
remove { CommandManager.RequerySuggested -= value; }
}
}
} // class CustomDeviceGUI
All the changes I had to make were in XAML. Primarily it was a matter of using the ancestor to get the right data context. I also switched to ContextMenu.ItemContainer instead of ContextMenu.Resources.
<ContextMenu.ItemContainerStyle>
<Style TargetType="MenuItem">
<Setter Property="Command" Value="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type Button}}, Path=DataContext.MyCommand}"/>
</Style>
</ContextMenu.ItemContainerStyle>
Eventough I'm not sure I think that the:
<Setter Property="Command" Value="{Binding MyCommand}"/>
binding needs a RoutedUICommand object.
EDIT:
Another thing that i have noticed is that you don't set any command bindings before. Like this:
<Window.CommandBindings>
<CommandBinding Command="MyCommand" Executed="Execute" />
</Window.CommandBindings>
just an example you can set CommandBindings to many others controls.