I'm building a Windows Store App using the OOTB Grid App template. I'm noticing that there is a 500-1000 millisecond pause when I am navigating from page to page either forward or backward. It is mainly noticeable when I hit the back button (the back arrow stays in the "Hit" state for the 500-1000 ms). Going forward isn't as bad because usually the screen has animations and transitions to fill most of the loading times.
My first thought was that there was something on in the LoadState method that was causing the slowdown, but the only thing I have that isn't from the template I'm running on a background thread and calling it with an async preface.
My second thought was that I was passing a complex object to each page's navigationParameter instead of just passing a simple string. I didn't think this could be the cause since the object should be pass by reference so there really shouldn't be any slowdown because I passed a non-string into the NavigateTo method.
(I haven't read any guidance about this, so I don't know if the page navigation is less snappy when passing non-strings between pages. If anyone has any insight into this, that would be wonderful)
My next thought was that my Xaml is too complex and the pause is the Xaml loading all of the items into the list and what not. This might be the issue and if so, I have no idea how to test or fix it. The UI feels fluid once everything is loaded (all of the items on the page scroll without stutter)
If this is the case, is there any way to show a loading circle with the Xaml generates and then once it is done generating, fade the content in and the circle out?
The main thing that I want to fix is I don't want the back button to "freeze" in the Hit. Any help or guidance would be great!
Basic app info:
Pages have combinations of List and Grid View controls with different Item templates. No images or graphics are used, but I do use a gradient brush on some of the item templates (not super complex, similar to the start screen item gradients). Most lists only have 20-30 items, some more most less.
The average page has 1 Item Source, and 2 Item Display controls, a list and a scroll viewer that holds the details of the selected item.
Details for any item are about 2-3 normal paragraphs of details text and 3-4 < 20 char strings.
EDIT: Project Code:
Page 1 code
protected async override void LoadState(Object navigationParameter, Dictionary<String, Object> pageState)
{
if (navigationParameter == null)
this.DefaultViewModel["Groups"] = GlobalData.Catalog.Catalog;
else
this.DefaultViewModel["Groups"] = navigationParameter;
await GlobalData.LibraryDownload.DiscoverActiveDownloadsAsync();
}
The DiscoverActiveDownloadsAsync method is the same code from this example code
SaveState, OnNavigateTo, and OnNavigateFrom methods haven't been modified from the LayoutAwarePage base class.
Page 2 code
protected async override void LoadState(Object navigationParameter, Dictionary<String, Object> pageState)
{
if (navigationParameter is CatalogBook)
{
var catBook = (CatalogBook)navigationParameter;
var book = catBook.Book;
await book.InitializeAsync();
this.DefaultViewModel["Group"] = catBook;
this.DefaultViewModel["Items"] = book.Items;
}
else if (navigationParameter is IBook)
{
var book = await Task.Run<IBook>(async () =>
{
var b = (IBook)navigationParameter;
await b.InitializeAsync();
return b;
});
this.DefaultViewModel["Group"] = book;
this.DefaultViewModel["Items"] = book.Chapters;
}
if (pageState == null)
{
// When this is a new page, select the first item automatically unless logical page
// navigation is being used (see the logical page navigation #region below.)
if (!this.UsingLogicalPageNavigation() && this.itemsViewSource.View != null)
{
this.itemsViewSource.View.MoveCurrentToFirst();
}
}
else
{
// Restore the previously saved state associated with this page
if (pageState.ContainsKey("SelectedItem") && this.itemsViewSource.View != null)
{
var number = 0;
if(!int.TryParse(pageState["SelectedItem"].ToString(), out number)) return;
var item = itemsViewSource.View.FirstOrDefault(i => i is ICanon && ((ICanon)i).Number == number);
if (item == null) return;
this.itemsViewSource.View.MoveCurrentTo(item);
itemListView.UpdateLayout();
itemListView.ScrollIntoView(item);
}
}
}
...
protected override void SaveState(Dictionary<String, Object> pageState)
{
if (this.itemsViewSource.View != null)
{
var selectedItem = this.itemsViewSource.View.CurrentItem;
pageState["SelectedItem"] = ((ICanon)selectedItem).Number;
}
}
The InitializeAsync method reads from an SQLite database some of the basic information about a book (chapters, author, etc.) and generally runs very quickly (< 10ms)
Grid code
I get the data by querying an SQLite database using the SQLite-net Nuget Package's async methods. The queries usually look something like this:
public async Task InitializeAsync()
{
var chapters = await _db.DbContext.Table<ChapterDb>().Where(c => c.BookId == Id).OrderBy(c => c.Number).ToListAsync();
Chapters = chapters
.Select(c => new Chapter(_db, c))
.ToArray();
HeaderText = string.Empty;
}
I populate the grids by using the following Xaml:
<CollectionViewSource
x:Name="groupedItemsViewSource"
Source="{Binding Groups}"
IsSourceGrouped="true"
ItemsPath="Items"
d:Source="{Binding DisplayCatalog, Source={d:DesignInstance Type=data:DataCatalog, IsDesignTimeCreatable=True}}"/>
<common:CatalogItemTemplateSelector x:Key="CatalogItemTemplateSelector" />
...
<GridView
Background="{StaticResource ApplicationPageLightBackgroundThemeBrushGradient}"
ItemsSource="{Binding Source={StaticResource groupedItemsViewSource}}"
SelectionMode="Multiple"
Grid.Row="1"
ItemTemplateSelector="{StaticResource CatalogItemTemplateSelector}"
IsItemClickEnabled="True"
ItemClick="ItemView_ItemClick" Margin="-40,0,0,0">
<GridView.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Horizontal" Height="628" Margin="120,10,0,0" />
</ItemsPanelTemplate>
</GridView.ItemsPanel>
<GridView.GroupStyle>
<GroupStyle>
<GroupStyle.HeaderTemplate>
<DataTemplate>
<Grid Margin="1,10,0,6">
<Button
AutomationProperties.Name="Group Title"
Click="Header_Click"
Style="{StaticResource TextPrimaryButtonStyle}" >
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Name}" Margin="3,-7,10,10" Style="{StaticResource GroupHeaderTextStyle}" />
<TextBlock Text="{StaticResource ChevronGlyph}" FontFamily="Segoe UI Symbol" Margin="0,-7,0,10" Style="{StaticResource GroupHeaderTextStyle}"/>
</StackPanel>
</Button>
</Grid>
</DataTemplate>
</GroupStyle.HeaderTemplate>
<GroupStyle.Panel>
<ItemsPanelTemplate>
<VariableSizedWrapGrid Margin="0,0,80,0" ItemHeight="{StaticResource ItemHeight}" ItemWidth="{StaticResource ItemWidth}"/>
</ItemsPanelTemplate>
</GroupStyle.Panel>
</GroupStyle>
</GridView.GroupStyle>
</GridView>
the CatalogItemTemplateSelector class looks like this:
public class CatalogItemTemplateSelector : DataTemplateSelector
{
protected override DataTemplate SelectTemplateCore(object item, DependencyObject container)
{
// cast item to your custom item class
var customItem = item as ICatalogItem;
if (customItem == null)
return null;
string templateName = String.Empty;
if (customItem is CatalogFolder || customItem is CatalogMoreFolder)
{
templateName = "FolderItemDataTemplate";
}
else if (customItem is CatalogBook || customItem is CatalogMoreBook)
{
templateName = "BookItemDataTemplate";
}
object template = null;
// find template in App.xaml
Application.Current.Resources.TryGetValue(templateName, out template);
return template as DataTemplate;
}
}
Both of the templates are ~20 lines of Xaml, nothing special
If there are other pieces of code that I haven't included, let me know and I'll add them.
What does your memory usage look like? Might you be paging to disk?
Excerpt From http://paulstovell.com/blog/wpf-navigation
Page Lifecycles ....
Suppose your page required some kind of parameter data to be passed to it:
... When navigating, if you click
"Back", WPF can't possibly know what values to pass to the
constructor; therefore it must keep the page alive.
... If you navigate passing an object directly, WPF will keep the object alive.
I don't have an answer, but this Channel 9 (Microsoft) video is pretty good regarding XAML performance. Maybe it can help you in your problem.
http://channel9.msdn.com/Events/Build/2012/4-103
Related
I have a large ListView which is largely made InkCanvas objects, it turns out that ListView implements data virtualisation to "cleverly" unload and load items in the view depending on the visible items in the view. The problem with this is that many times the ListView caches items and when a new item is added it essentially copy items already added in the view. So in my case, if the user adds a stroke to an Inkcanvas and then adds a new InkCanvas to the ListView, the new canvas contains the strokes from the previous canvas. As reported here this is because of the data virtualisation. My ListView is implemented as follows:
<Grid HorizontalAlignment="Stretch">
<ListView x:Name="CanvasListView" IsTapEnabled="False"
IsItemClickEnabled="False"
ScrollViewer.ZoomMode="Enabled"
ScrollViewer.HorizontalScrollMode="Enabled"
ScrollViewer.VerticalScrollMode="Enabled"
ScrollViewer.HorizontalScrollBarVisibility="Auto"
ScrollViewer.VerticalScrollBarVisibility="Visible"
HorizontalAlignment="Stretch">
<!-- Make sure that items are not clickable and centered-->
<ListView.ItemContainerStyle>
<Style TargetType="ListViewItem">
<Setter Property="HorizontalContentAlignment" Value="Center"/>
</Style>
</ListView.ItemContainerStyle>
<ListView.ItemTemplate>
<DataTemplate>
<StackPanel>
<local:CanvasControl Margin="0 2"
VerticalAlignment="Stretch"
HorizontalAlignment="Stretch"
MinWidth="1000" MinHeight="100" MaxHeight="400"
Background="LightGreen"/>
<Grid HorizontalAlignment="Stretch" Background="Black" Height="2"></Grid>
</StackPanel>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
<StackPanel Orientation="Horizontal" HorizontalAlignment="Stretch">
<InkToolbar x:Name="inkToolbar"
VerticalAlignment="Top"
Background="LightCoral"/>
<StackPanel HorizontalAlignment="Right">
<Button x:Name="AddButton" Content="Add Page" Click="Button_Click"/>
<TextBlock x:Name="PageCountText" />
</StackPanel>
</StackPanel>
</Grid>
A full example can be found here and here is a video of the issue.
Indeed if I turn off data virtualisation (or switch to an ItemsControl) everything works brilliantly. The problem however is that with a very large list, this approach has a heavy impact on performance (with 60+ InkCanvas controls the app just crashes). So is there a way to retain data virtualisation while avoiding the duplication of items? I have tried with VirtualizationMode.Standard but items are still duplicated.
To solve this problem, we must first understand why this problem occurs.
ListView has a reuse container inside, it will not endlessly create new list items, but will recycle.
In most cases, such recycling is not a problem. But it's special for InkCanvas.
InkCanvas is a stateful control. When you draw on InkCanvas, the handwriting is retained and displayed on the UI.
If your control is a TextBlock, this problem does not occur, because we can directly bind the value to TextBlock.Text, but for the Stroke of InkCanvas, we cannot directly bind, which will cause the so-called residue.
So in order to avoid this, we need to clear the state, that is, every time the InkCanvas is created or reloaded, the strokes in the InkCanvas are re-rendered.
1. Create a list for saving stroke information in ViewModel
public class ViewModel : INotifyPropertyChanged
{
// ... other code
public List<InkStroke> Strokes { get; set; }
public ViewModel()
{
Strokes = new List<InkStroke>();
}
}
2. Change the internal structure of CanvasControl
xaml
<Grid>
<InkCanvas x:Name="inkCanvas"
Margin="0 2"
MinWidth="1000"
MinHeight="300"
HorizontalAlignment="Stretch" >
</InkCanvas>
</Grid>
xaml.cs
public sealed partial class CanvasControl : UserControl
{
public CanvasControl()
{
this.InitializeComponent();
// Set supported inking device types.
inkCanvas.InkPresenter.InputDeviceTypes =
Windows.UI.Core.CoreInputDeviceTypes.Mouse |
Windows.UI.Core.CoreInputDeviceTypes.Pen;
}
private void StrokesCollected(InkPresenter sender, InkStrokesCollectedEventArgs args)
{
if (Data != null)
{
var strokes = inkCanvas.InkPresenter.StrokeContainer.GetStrokes().ToList();
Data.Strokes = strokes.Select(p => p.Clone()).ToList();
}
}
public ViewModel Data
{
get { return (ViewModel)GetValue(DataProperty); }
set { SetValue(DataProperty, value); }
}
public static readonly DependencyProperty DataProperty =
DependencyProperty.Register("Data", typeof(ViewModel), typeof(CanvasControl), new PropertyMetadata(null,new PropertyChangedCallback(Data_Changed)));
private static void Data_Changed(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
if(e.NewValue!=null && e.NewValue is ViewModel vm)
{
var strokes = vm.Strokes.Select(p=>p.Clone());
var instance = d as CanvasControl;
instance.inkCanvas.InkPresenter.StrokesCollected -= instance.StrokesCollected;
instance.inkCanvas.InkPresenter.StrokeContainer.Clear();
try
{
instance.inkCanvas.InkPresenter.StrokeContainer.AddStrokes(strokes);
}
catch (Exception)
{
}
instance.inkCanvas.InkPresenter.StrokesCollected += instance.StrokesCollected;
}
}
}
In this way, we can keep our entries stable.
I have a datagrid used to display the log information which read from a data server.
As the log number is huge, I don't want to load data at once when page initialize. So below is my design:
Load the first 20 lines in initialize. When user drag the scrollbar, I will calculate the distance which the slider moved, get the index of log and then retrieve new log from server. The old log will be cleared to make sure the datagrid's memory consumption is not large.
But during implement, I met following problem
1. How to set the size of slider? If I only add 20 lines to datagrid, the slider is very long
2. During I drag the slider(not complete), the content of datagride will also change automatically. How should I disable it.
Below is my code:
<DataGrid x:Name="LogsGrid"
Margin="0,0,0,0"
Height="457"
HeadersVisibility="Column"
ItemsSource="{Binding LogsList}"
SelectedItem="{Binding SelectedRow, Mode=TwoWay}"
ScrollViewer.CanContentScroll="True">
<i:Interaction.Triggers>
<local:RoutedEventTrigger RoutedEvent="ScrollViewer.ScrollChanged">
<local:CustomCommandAction Command="{Binding ScrollCommand}" />
</local:RoutedEventTrigger>
</i:Interaction.Triggers>
<DataGrid.Columns>
...
</DataGrid.Columns>
Blew is my viewmodel:
public LogsViewModel()
{
InitializeCommand();
scrollTimer = new DispatcherTimer();
scrollTimer.Interval = new TimeSpan(0,0,0,0,250);
scrollTimer.Tick += new EventHandler(ScrollTimer_Elapsed);
}
private void ScrollTimer_Elapsed(object sender, EventArgs e)
{
scrollTimer.Stop();
GetNewLog(nCurrentScrollIndex);
}
private int nCurrentScrollIndex = 0;
private void OnScroll(object param)
{
ScrollChangedEventArgs args = param as ScrollChangedEventArgs;
if (args.VerticalOffset == 0)
{
return;
}
int nLogTotalNumber = int.Parse(LogTotalNumber);
nCurrentScrollIndex = (int)(args.VerticalOffset/args.ExtentHeight * nLogTotalNumber);
if (scrollTimer!= null || scrollTimer.IsEnabled)
{
scrollTimer.Stop();
}
scrollTimer.Start();
}
I don't think it's necessary going into the nuts-n-bolts of WPF like this. DataGrid supports virtualization, so unless you've done something to break it it should only fetch the items actually visible on-screen. That said, I've never been able to get DataGrid to properly fetch items asynchronously without locking up the GUI thread, so you'll probably have to handle this yourself in the view model.
Here's an example that easily manages 10 million records on my i7, I'll start by templating the DataGrid:
<DataGrid ItemsSource="{Binding Items}" AutoGenerateColumns="False">
<DataGrid.Columns>
<DataGridTemplateColumn Header="Text">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBlock Text="{Binding Text}" />
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
</DataGrid>
Here's the main view model, which creates an array of items and fetching each one only when requested:
public class MainViewModel : ViewModelBase
{
public Item[] Items { get; }
public MainViewModel()
{
this.Items = Enumerable
.Range(1, 1000)
.Select(i => new Item { Value = i })
.ToArray();
}
}
public class Item : ViewModelBase
{
public int Value { get; set; }
private string _Text;
public string Text
{
get
{
// if already loaded then return what we got
if (!String.IsNullOrEmpty(this._Text))
return this._Text;
// otherwise load the value in a task (this will raise property changed event)
Task.Run(async () => {
Debug.WriteLine($"Fetching value # {Value}");
await Task.Delay(1000); // simulate delay in fetching the value
this.Text = $"Item # {Value}";
});
// return default string for now
return "Loading...";
}
set
{
if (this._Text != value)
{
this._Text = value;
RaisePropertyChanged(() => this.Text);
}
}
}
}
This is a very basic example of course, shown only to demonstrate the point. In the real world you would want to set up some kind of cache, and fetch blocks of records at a time. You'd probably also want some kind of prioritized pre-meditated queuing that anticipates records likely to be fetched in future. Either way, WPF doesn't support data virtualization, so I think you're going to have to wind up doing something like this to get the exact behavior you're after.
I want to implement a GridView which takes 3 items in a row, and if the number of items are 2 in last row, then the last row items should be aligned center instead of being left-aligned. Here are a couple of images to explain what I want to achieve.
Currently my implementation looks like
.
And this is what I want to achieve.
Any help would be appreciated.
There are many ways realizing the feature that you mentioned.
To summarize it, you need to inherit GridView and override MeasureOverride ArrangeOverride method to re-calculate each Rect of Panel's children. This way is complex. For more info you could refer to
XAML custom panels overview.
And you could also use PrepareContainerForItemOverride method to re-layout the item directly.
<local:VariableGrid
x:Name="MyGridView"
SelectionMode="Single"
IsSwipeEnabled="False">
<local:VariableGrid.ItemTemplate >
<DataTemplate>
<StackPanel BorderBrush="Red" BorderThickness="3" Height="200" Width="200" Margin="20">
</StackPanel>
</DataTemplate>
</local:VariableGrid.ItemTemplate>
<local:VariableGrid.ItemsPanel>
<ItemsPanelTemplate>
<VariableSizedWrapGrid
Orientation="Horizontal"
VerticalAlignment="Top"
ScrollViewer.HorizontalScrollMode="Enabled"
ScrollViewer.VerticalScrollMode="Disabled"
MaximumRowsOrColumns="4">
</VariableSizedWrapGrid>
</ItemsPanelTemplate>
</local:VariableGrid.ItemsPanel>
</local:VariableGrid>
VariableGrid.cs
public sealed class VariableGrid : GridView
{
public VariableGrid()
{
this.DefaultStyleKey = typeof(VariableGrid);
}
protected override void PrepareContainerForItemOverride(DependencyObject element, object item)
{
var list = this.ItemsSource as List<string>;
var griditem = element as GridViewItem;
for (var t = ((list.Count - list.Count % 4)); t < list.Count; t++)
{
if (item as string == list[t])
{
if (griditem != null)
{
VariableSizedWrapGrid.SetColumnSpan(griditem, 2);
}
}
}
base.PrepareContainerForItemOverride(element, item);
}
}
However, this simple way can not fit all the scenario.
I'm having problems finding a control (ToggleSwitch) inside my ListView. I have tried several approaches found here on SO or on other places around the web but none seems to be working.
Here is a the listView markup
<ListView Name="LampsListView" ItemsSource="{x:Bind Lamps}">
<ListView.ItemTemplate>
<DataTemplate x:DataType="models:Lamp">
<StackPanel Name="StackPanel">
<TextBlock Margin="10,0" Text="{Binding Name}" VerticalAlignment="Center" HorizontalAlignment="Left" />
<ToggleSwitch Margin="10,0" HorizontalAlignment="Right" Name="LampToggleSwitch" IsOn="{x:Bind State, Converter={ StaticResource IntToIsOn}}" />
</StackPanel>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
I have tried the ContainerFromItem but x will always be null.
foreach (var item in this.LampsListView.Items)
{
var x = this.LampsListView.ContainerFromItem(item);
}
And also the GetChildren approach but even thought GetChildren returns items it wont give me anything I can work with.
private void FindMyStuff()
{
var ch = this.GetChildren(this.LampsListView);
}
private List<FrameworkElement> GetChildren(DependencyObject parent)
{
List<FrameworkElement> controls = new List<FrameworkElement>();
for (int i = 0; i < VisualTreeHelper.GetChildrenCount(parent); ++i)
{
var child = VisualTreeHelper.GetChild(parent, i);
if (child is FrameworkElement)
{
controls.Add(child as FrameworkElement);
}
controls.AddRange(this.GetChildren(child));
}
return controls;
}
And I've tried booth finding the StackPanel and go straight for the LampToggleSwitch.
The FindMyStuff() is called right after i've updated the ObservableCollection that is bound to the ListView and the update is done from a this.Dispatcher.RunAsync(). I don't know if this has anything to do with it thought.
Could someone please tell me what I'm doing wrong?
Generally traversing visual tree or getting items by names/types is in most cases a wrong way of doing thigs, much better would be to implement apropriate binding.
Nevertheless if you want to do this, you are almost there. As I've tried it should work like this:
var listViewItem = this.mylist.ContainerFromItem(mylist.Items.First()) as ListViewItem;
var itemsStackPanel = listViewItem.ContentTemplateRoot as StackPanel;
var myToggleSwitch = itemsStackPanel.Children.FirstOrDefault(x => x is ToggleSwitch);
// other way with your helper
var childByHelper = GetChildren(listViewItem).FirstOrDefault(x => x is ToggleSwitch);
Just watch out when you run this, if it's done before list is populated, listVieItems will be null.
I've been working on this problem for a stupid amount of time. It is time to ask for directions despite my inner man saying "don't do it."
I am coding in WPF C# using MVVM design pattern. We try to adhere strictly to the pattern and put nothing in the code behind unless there is no option or it is completely unreasonable to do so. Having said that, I am working with a Telerik RadTreeView. Here is a snippet of it in my XAML:
<telerik:RadTreeView IsExpandOnSingleClickEnabled="True" IsLineEnabled="True" Margin="5"
ItemsSource="{Binding ItemsView}"
SelectedItem="{Binding SelectedWidget, Mode=TwoWay}"
ItemTemplate="{StaticResource NodeTemplate}" />
Currently the tree is working properly so that if you highlight a tree item and click the OK button on the view, all is good. However, I need to also allow the user to double click on one of the tree items. This means I already have a command and method, protected override void OkAction(), in my view model with the needed logic. Telerik supplies a property called ItemDoubleClick that is supposed to supply functionality for the tree item double click. But I can't find anything to allow me to do this in the view model. In other words, how do I do the binding? We also have a behavior setup in our project for double clicking that I was told I could use, but I have no experience with behaviors. I'm still a little wet with WPF.
If it helps, here is a link to the documentation for Telerik: http://www.telerik.com/help/wpf/radtreeview-events-overview.html
I would appreciate any help or direction anyone can provide.
Try this out Stan:
<Grid.Resources>
<DataTemplate x:Key="WidgetTemplate">
<StackPanel Orientation="Horizontal">
<Image Source="/Resources/gear1.png" Margin="1" Stretch="None" />
<TextBlock Text="{Binding Name}" VerticalAlignment="Center" Margin="6,0,0,0" />
</StackPanel>
</DataTemplate>
<HierarchicalDataTemplate x:Key="NodeTemplate" ItemsSource = "{Binding Children}" ItemTemplate="{StaticResource WidgetTemplate}">
<TextBlock Text="{Binding Name}"/>
</HierarchicalDataTemplate>
</Grid.Resources>
This is where you are going to want to possibly use the Attached Behavior that you already have for the DoubleClick.
Otherwise, here is the complete code that I use which creates the Attached Behavior and will create two Attached Properties which bind to a Command and optionally a Command Parameter.
AttachedBehaviors.cs
public static class MouseDoubleClick
{
public static DependencyProperty CommandProperty =
DependencyProperty.RegisterAttached("Command",
typeof(ICommand),
typeof(MouseDoubleClick),
new UIPropertyMetadata(CommandChanged));
public static DependencyProperty CommandParameterProperty =
DependencyProperty.RegisterAttached("CommandParameter",
typeof(object),
typeof(MouseDoubleClick),
new UIPropertyMetadata(null));
public static void SetCommand(DependencyObject target, ICommand value)
{
target.SetValue(CommandProperty, value);
}
public static void SetCommandParameter(DependencyObject target, object value)
{
target.SetValue(CommandParameterProperty, value);
}
public static object GetCommandParameter(DependencyObject target)
{
return target.GetValue(CommandParameterProperty);
}
private static void CommandChanged(DependencyObject target, DependencyPropertyChangedEventArgs e)
{
Control control = target as Control;
if (control != null)
{
if ((e.NewValue != null) && (e.OldValue == null))
{
control.MouseDoubleClick += OnMouseDoubleClick;
}
else if ((e.NewValue == null) && (e.OldValue != null))
{
control.MouseDoubleClick -= OnMouseDoubleClick;
}
}
}
private static void OnMouseDoubleClick(object sender, RoutedEventArgs e)
{
Control control = sender as Control;
ICommand command = (ICommand)control.GetValue(CommandProperty);
object commandParameter = control.GetValue(CommandParameterProperty);
if (command.CanExecute(commandParameter))
command.Execute(commandParameter);
}
}
.xaml - Remember to add the namespace of where the Attached Behavior lies.
<telerik:RadTreeView IsExpandOnSingleClickEnabled="True"
IsLineEnabled="True"
Margin="5"
ItemsSource="{Binding ItemsView}"
SelectedItem="{Binding SelectedWidget, Mode=TwoWay}"
ItemTemplate="{StaticResource NodeTemplate}"
acb:MouseDoubleClick.Command="{Binding ShowItemCommand}" />
SampleViewModel.cs
private RelayCommand _showItemCommand;
public RelayCommand ShowItemCommand
{
get
{
return _showItemCommand ?? (_showItemCommand =
new RelayCommand(ShowItemDetails, IsItemSelected));
}
}
obviously I don't have Telerik code so I can't really help as much as i would like to but you can try something like this. (Disclaimer: I am writing from top of my head)
Define your style in Grid.Resources
<Style TargetType="{x:Type RadTreeViewItem }" x:Key="TreeViewItemStyle">
<EventSetter Event="MouseDoubleClick" Handler="{Binding DoubleClick}" />
</Style>
Add the Style to Container Style.
<telerik:RadTreeView IsExpandOnSingleClickEnabled="True" IsLineEnabled="True" Margin="5"
ItemsSource="{Binding ItemsView}"
SelectedItem="{Binding SelectedWidget, Mode=TwoWay}"
ItemTemplate="{StaticResource NodeTemplate}"
ItemContainerStyle ="{StaticResource TreeViewItemStyle}"/>
Let me know if it works.
I tried several ways to get this accomplished.In the end I found that VS2012 was giving me fits. I noticed that changes weren't being applied on a build and run.
I opened VS2010 to find I wasn't experiencing the same issues. After speaking with my boss, we found this to be a good example of a situation that adhering to MVVM may not be the wisest choice since double clicking was strictly UI related.
I simply bounced through the code behind and into the view model using the instantiation of the view model as the data context. Didn't take but a second to do that.
As for the other solutions, I am sure it is completely possible, but I cannot confirm or deny the posts I've made here because of my VS2012 issues.