I am making this app for windows phone 7, what I do is retrieve all the images from camera roll, saved pictures and other folder and display them in the listbox inside a wrap panel so they are displayed side by side....the thumbnail of the images is actually displayed hear.....
but as the number of images are increasing UI gets very slow and scrolling takes time...
I read many post and other question I think data virtualization or lazy loading is what I need but I am not understanding how can I use it, I saw the post from shawn oster and peter torr.....
I use a backgroundworker to load the images...
here's how...
void backroungWorker1_DoWork(object sender, DoWorkEventArgs e)
{
Dispatcher.BeginInvoke(() =>
{
foreach (string fileName in fileStorage.GetFileNames("images//*.*"))
{
if (fileName == null)
break;
string filepath = System.IO.Path.Combine("images", fileName);
try
{
using (IsolatedStorageFileStream imageStream = fileStorage.OpenFile(filepath, FileMode.Open))
{
var imageSource = PictureDecoder.DecodeJpeg(imageStream);
BitmapImage bitmapImage = new BitmapImage();
bitmapImage.SetSource(imageStream);
var item = new ImageToName { bmp = bitmapImage, FileName = fileName };
vltBitmapImage.Add(item);
imageStream.Dispose();
imageStream.Close();
}
}
catch
{
Exception x = new Exception();
}
}
if (vltBitmapImage.Count() != 0)
{
lone.Visibility = Visibility.Collapsed;
this.vaultbox.ItemsSource = vltBitmapImage;
}
else
lone.Visibility = Visibility.Visible;
});
}
any help is greatly appreciated.....
sorry for being a noob...
Try this sample from code project, it explain how it work and comes with a full sample project
See: Loading Data when the User scrolls to the end of the list
If you want to add a Lazy load to a listbox you must setup a listner for your list box and change the way you load data into your data model, so first lest set up at the XAML code for a listbox:
In your page resource add this style, and note the loaded event its include ("ScrollViewer_Loaded"):
...
<phone:PhoneApplicationPage.Resources>
<Style x:Key="BusinessListBoxStyle"
TargetType="ListBox">
<Setter Property="Background"
Value="Transparent" />
<Setter Property="Foreground"
Value="{StaticResource PhoneForegroundBrush}" />
<Setter Property="ScrollViewer.HorizontalScrollBarVisibility"
Value="Disabled" />
<Setter Property="ScrollViewer.VerticalScrollBarVisibility"
Value="Auto" />
<Setter Property="BorderThickness"
Value="0" />
<Setter Property="BorderBrush"
Value="Transparent" />
<Setter Property="Padding"
Value="0" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="ListBox">
<ScrollViewer x:Name="scrollViewer"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}"
Background="{TemplateBinding Background}"
Foreground="{TemplateBinding Foreground}"
Padding="{TemplateBinding Padding}"
Loaded="ScrollViewer_Loaded">
<ItemsPresenter />
</ScrollViewer>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</phone:PhoneApplicationPage.Resources>
...
The add a reference to your style for the listbox, and bind your itemsSource to a list of items from your viewModel.
...
<ListBox x:Name="myList"
ItemsSource="{Binding myDataSource}"
Style="{StaticResource BusinessListBoxStyle}">
...
Next you have to set some code that loads data into the datamodel, when you reach the end of the list element in the list (the datamodel start loading more data into the mode, adds more items) Lazyloading!!
The way i normaly do this is by listing to the vertical offset of the listbox's scrollbar, and if its is about 1/4 from the edge i starts loading more items into the datamodel.
In the ScrollViewer loaded handler i set up at VertialOffset listener, by using the DependencyProperty, see code below:
public static readonly DependencyProperty ListVerticalOffsetProperty =
DependencyProperty.Register(
"ListVerticalOffset",
typeof(double),
typeof(MyPage),
new PropertyMetadata(new PropertyChangedCallback(OnListVerticalOffsetChanged))
);
private ScrollViewer _listScrollViewer;
private void ScrollViewer_Loaded(object sender, RoutedEventArgs e)
{
_listScrollViewer = sender as ScrollViewer;
Binding binding = new Binding();
binding.Source = _listScrollViewer;
binding.Path = new PropertyPath("VerticalOffset");
binding.Mode = BindingMode.OneWay;
this.SetBinding(ListVerticalOffsetProperty, binding);
}
public double ListVerticalOffset
{
get { return (double)this.GetValue(ListVerticalOffsetProperty); }
set { this.SetValue(ListVerticalOffsetProperty, value); }
}
private double _lastFetch;
private static void OnListVerticalOffsetChanged(DependencyObject obj, DependencyPropertyChangedEventArgs e)
{
MyPage page = obj as MyPage;
ScrollViewer viewer = page._listScrollViewer;
if (viewer != null)
{
if (page._lastFetch < viewer.ScrollableHeight)
{
// Trigger within 1/4 the viewport.
if (viewer.VerticalOffset >= (viewer.ScrollableHeight - (viewer.ViewportHeight / 4)))
{
page._lastFetch = viewer.ScrollableHeight;
MyViewModel _tmpviewmodel = page.DataContext as MyViewModel;
if ((_tmpviewmodel != null) && (_tmpviewmodel.HasMoreItems))
_tmpviewmodel.GetMoreItems();
}
}
}
}
Note here i make use of a MyViewModel, that holds all the items the listbox i binded to, and has methods for load items from a database, the isolatedstore, the web or what ever your needs are.
You just have to find your way of only load a part of your data into the viewmodel. I your case i will first load a list of all the files you need to load (that is just to retreive the list from GetFileNames in the IsoLatedStore). Then mayby only loads 20 pics at the time!
Related
I've been working with WPF treeview for a bit recently and I'm having a really awful time trying to get the selected item to show up on the screen when the user uses a search function that sets the IsSelected property on the backing object.
Currently my approach is using the method in this answer: https://stackoverflow.com/a/34620549/800318
private void FocusTreeViewNode(TreeViewEntry node)
{
if (node == null) return;
var nodes = (IEnumerable<TreeViewEntry>)LeftSide_TreeView.ItemsSource;
if (nodes == null) return;
var stack = new Stack<TreeViewEntry>();
stack.Push(node);
var parent = node.Parent;
while (parent != null)
{
stack.Push(parent);
parent = parent.Parent;
}
var generator = LeftSide_TreeView.ItemContainerGenerator;
while (stack.Count > 0)
{
var dequeue = stack.Pop();
LeftSide_TreeView.UpdateLayout();
var treeViewItem = (TreeViewItem)generator.ContainerFromItem(dequeue);
if (stack.Count > 0)
{
treeViewItem.IsExpanded = true;
}
else
{
if (treeViewItem == null)
{
//This is being triggered when it shouldn't be
Debugger.Break();
}
treeViewItem.IsSelected = true;
}
treeViewItem.BringIntoView();
generator = treeViewItem.ItemContainerGenerator;
}
}
TreeViewEntry is my backing data type, which has a reference to its parent node. Leftside_TreeView is the virtualized TreeView that is bound to the list of my objects. Turning off virtualization is not an option as performance is really bad with it off.
When I search for an object and the backing data object is found, I call this FocusTreeViewNode() method with the object as its parameter. It will typically work on the first call, selecting the object and bringing it into view.
Upon doing the search a second time, the node to select is passed in, however the ContainerFromItem() call when the stack is emptied (so it is trying to generate the container for the object itself) returns null. When I debug this I can see the object I am searching for in the ContainerGenerator's items list, but for some reason it is not being returned. I looked up all the things to do with UpdateLayout() and other things, but I can't figure this out.
Some of the objects in the container may be off the page even after the parent node is brought into view - e.g. an expander has 250 items under it and only 60 are rendered at time. Could this be an issue?
Update
Here is a sample project that makes a virtualized treeview that shows this issue. https://github.com/Mgamerz/TreeViewVirtualizingErrorDemo
Build it in VS, then in the search box enter something like 4. Press search several times and it will throw an exception saying the container was null, even though if you open the generator object you can clearly see it's in the generator.
Like many other aspects of WPF development, this operation can be handled by using the MVVM design pattern.
Create a ViewModel class, including an IsSelected property, which holds the data for each tree item.
Bringing the selected item into view can then be handled by an attached property
public static class perTreeViewItemHelper
{
public static bool GetBringSelectedItemIntoView(TreeViewItem treeViewItem)
{
return (bool)treeViewItem.GetValue(BringSelectedItemIntoViewProperty);
}
public static void SetBringSelectedItemIntoView(TreeViewItem treeViewItem, bool value)
{
treeViewItem.SetValue(BringSelectedItemIntoViewProperty, value);
}
public static readonly DependencyProperty BringSelectedItemIntoViewProperty =
DependencyProperty.RegisterAttached(
"BringSelectedItemIntoView",
typeof(bool),
typeof(perTreeViewItemHelper),
new UIPropertyMetadata(false, BringSelectedItemIntoViewChanged));
private static void BringSelectedItemIntoViewChanged(DependencyObject obj, DependencyPropertyChangedEventArgs args)
{
if (!(args.NewValue is bool))
return;
var item = obj as TreeViewItem;
if (item == null)
return;
if ((bool)args.NewValue)
item.Selected += OnTreeViewItemSelected;
else
item.Selected -= OnTreeViewItemSelected;
}
private static void OnTreeViewItemSelected(object sender, RoutedEventArgs e)
{
var item = e.OriginalSource as TreeViewItem;
item?.BringIntoView();
// prevent this event bubbling up to any parent nodes
e.Handled = true;
}
}
This can then be used as part of a style for TreeViewItems
<Style x:Key="perTreeViewItemContainerStyle"
TargetType="{x:Type TreeViewItem}">
<!-- Link the properties of perTreeViewItemViewModelBase to the corresponding ones on the TreeViewItem -->
<Setter Property="IsExpanded" Value="{Binding IsExpanded, Mode=TwoWay}" />
<Setter Property="IsSelected" Value="{Binding IsSelected, Mode=TwoWay}" />
<Setter Property="IsEnabled" Value="{Binding IsEnabled}" />
<!-- Include the two "Scroll into View" behaviors -->
<Setter Property="vhelp:perTreeViewItemHelper.BringSelectedItemIntoView" Value="True" />
<Setter Property="vhelp:perTreeViewItemHelper.BringExpandedChildrenIntoView" Value="True" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type TreeViewItem}">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"
MinWidth="14" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<ToggleButton x:Name="Expander"
Grid.Row="0"
Grid.Column="0"
ClickMode="Press"
IsChecked="{Binding Path=IsExpanded, RelativeSource={RelativeSource TemplatedParent}}"
Style="{StaticResource perExpandCollapseToggleStyle}" />
<Border x:Name="PART_Border"
Grid.Row="0"
Grid.Column="1"
Padding="{TemplateBinding Padding}"
Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}">
<ContentPresenter x:Name="PART_Header"
Margin="0,2"
HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
ContentSource="Header" />
</Border>
<ItemsPresenter x:Name="ItemsHost"
Grid.Row="1"
Grid.Column="1" />
</Grid>
<ControlTemplate.Triggers>
<Trigger Property="IsExpanded" Value="false">
<Setter TargetName="ItemsHost" Property="Visibility" Value="Collapsed" />
</Trigger>
<Trigger Property="HasItems" Value="false">
<Setter TargetName="Expander" Property="Visibility" Value="Hidden" />
</Trigger>
<!-- Use the same colors for a selected item, whether the TreeView is focussed or not -->
<Trigger Property="IsSelected" Value="true">
<Setter TargetName="PART_Border" Property="Background" Value="{DynamicResource {x:Static SystemColors.HighlightBrushKey}}" />
<Setter Property="Foreground" Value="{DynamicResource {x:Static SystemColors.HighlightTextBrushKey}}" />
</Trigger>
<Trigger Property="IsEnabled" Value="false">
<Setter Property="Foreground" Value="{DynamicResource {x:Static SystemColors.GrayTextBrushKey}}" />
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<Style TargetType="{x:Type TreeView}">
<Setter Property="ItemContainerStyle" Value="{StaticResource perTreeViewItemContainerStyle}" />
</Style>
More details and a full example of usage on my recent blog post.
Update 13 Oct
The blog post has been amended for when running in standard (non-lazy loading mode). The associated demo project shows a nested data structure of over 400,000 elements being displayed in a TreeView, and yet the response for selecting any random node is instantaneous.
It's quite difficult to get the TreeViewItem for a given data item, in all cases, especially the virtualized ones.
Fortunately, Microsoft has provided a helper function for us here How to: Find a TreeViewItem in a TreeView that I have adapted so it doesn't need a custom VirtualizingStackPanel class (requires .NET Framework 4.5 or higher, for older versions, consult the link above).
Here is how you can replace your FocusTreeViewNode method:
private void FocusTreeViewNode(MenuItem node)
{
if (node == null)
return;
var treeViewItem = GetTreeViewItem(tView, node);
treeViewItem?.BringIntoView();
}
public static TreeViewItem GetTreeViewItem(ItemsControl container, object item)
{
if (container == null)
throw new ArgumentNullException(nameof(container));
if (item == null)
throw new ArgumentNullException(nameof(item));
if (container.DataContext == item)
return container as TreeViewItem;
if (container is TreeViewItem && !((TreeViewItem)container).IsExpanded)
{
container.SetValue(TreeViewItem.IsExpandedProperty, true);
}
container.ApplyTemplate();
if (container.Template.FindName("ItemsHost", container) is ItemsPresenter itemsPresenter)
{
itemsPresenter.ApplyTemplate();
}
else
{
itemsPresenter = FindVisualChild<ItemsPresenter>(container);
if (itemsPresenter == null)
{
container.UpdateLayout();
itemsPresenter = FindVisualChild<ItemsPresenter>(container);
}
}
var itemsHostPanel = (Panel)VisualTreeHelper.GetChild(itemsPresenter, 0);
var children = itemsHostPanel.Children;
var virtualizingPanel = itemsHostPanel as VirtualizingPanel;
for (int i = 0, count = container.Items.Count; i < count; i++)
{
TreeViewItem subContainer;
if (virtualizingPanel != null)
{
// this is the part that requires .NET 4.5+
virtualizingPanel.BringIndexIntoViewPublic(i);
subContainer = (TreeViewItem)container.ItemContainerGenerator.ContainerFromIndex(i);
}
else
{
subContainer = (TreeViewItem)container.ItemContainerGenerator.ContainerFromIndex(i);
subContainer.BringIntoView();
}
if (subContainer != null)
{
TreeViewItem resultContainer = GetTreeViewItem(subContainer, item);
if (resultContainer != null)
return resultContainer;
subContainer.IsExpanded = false;
}
}
return null;
}
private static T FindVisualChild<T>(Visual visual) where T : Visual
{
for (int i = 0; i < VisualTreeHelper.GetChildrenCount(visual); i++)
{
if (VisualTreeHelper.GetChild(visual, i) is Visual child)
{
if (child is T item)
return item;
item = FindVisualChild<T>(child);
if (item != null)
return item;
}
}
return null;
}
I am facing the problem that I cannot open an anchorable of type X after I have loaded my old layout. This happens only when I have closed the anchorable of type X before saving the layout.
Does anyone have a similar problem with AvalonDock? Is this a bug of AvalonDock? After years of debugging, I fear that the binding <Setter Property="IsActive" Value="{Binding Model.IsActive, Mode=TwoWay}"/> doesn't get updated correctly in the view, when changing IsActive in the ViewModel. AvalonDock should be responsible for this task. But maybe the problem is the loading and saving of the layout?
The code
View
I am loading the saved layout of my anchorables (= tool windows) in the Loaded Event of my DockingManager in my View like this (simplified):
string savedLayout = Properties.Settings.Default.Layout;
XmlDocument doc = new XmlDocument();
doc.LoadXml(savedLayout);
// very simplified code. load saved xml layout and add anchorables to the dockmanager
doc.SelectNodes("//LayoutAnchorable").OfType<XmlNode>().ToList().ForEach(anchorable =>
{
this.DockManager.AnchorablesSource.Add(anchorable);
});
I am saving the current layout of my anchorables in the Closing Event of my MainWindow in my View like this (simplified):
XmlDocument doc = new XmlDocument();
XmlLayoutSerializer xmlLayoutSerializer = new XmlLayoutSerializer(this.DockManager);
using (MemoryStream stream = new MemoryStream())
{
xmlLayoutSerializer.Serialize(stream);
stream.Seek(0, SeekOrigin.Begin);
doc.Load(stream);
}
// here happens some magic. i think this code is not responsible for my problem
Properties.Settings.Default.Layout = doc.OuterXml;
The ViewModel is bound to the ViewModel in the XAML like this (simplified):
<xcad:DockingManager x:Name="DockManager" AnchorablesSource="{Binding Tools}" Loaded="DockManager_Loaded">
<xcad:DockingManager.LayoutItemContainerStyle>
<Style TargetType="{x:Type dockctrl:LayoutItem}">
<Setter Property="Title" Value="{Binding Model.ContentId}" />
<Setter Property="IsSelected" Value="{Binding Model.IsSelected, Mode=TwoWay}" />
<Setter Property="CanClose" Value="{Binding Model.CanClose, Mode=TwoWay}" />
<Setter Property="Visibility" Value="{Binding Model.IsVisible, Mode=TwoWay, Converter={StaticResource Bool2vis}, ConverterParameter={x:Static Visibility.Hidden}}"/>
<Setter Property="CloseCommand" Value="{Binding Model.CloseCommand}" />
<Setter Property="IconSource" Value="{Binding Model.IconSource}" />
<Setter Property="IsActive" Value="{Binding Model.IsActive, Mode=TwoWay}"/>
<Setter Property="ContentId" Value="{Binding Model.ContentId}" />
</Style>
</xcad:DockingManager.LayoutItemContainerStyle>
[...]
ViewModel
The anchorable is opened in the ViewModel of the MainWindow. Here is the example code for the messages:
public ObservableCollection<ToolBoxViewModelBase> Tools { get; } = new ObservableCollection<ToolBoxViewModelBase>();
public MainWindowViewModel()
{
// [...]
this.MessagesWindow = new MessagesWindowViewModel();
SimpleIoc.Default.Register<MessagesWindowViewModel>(() => this.MessagesWindow);
this.ShowMessagesWindowCommand = new RelayCommand(() => this.OpenToolBox(this.MessagesWindow));
// [...]
}
public void OpenToolBox<T>(T viewModel) where T : ToolBoxViewModelBase
{
// [...]
viewModel.IsVisible = true;
viewModel.IsActive = true;
// [...]
}
Just let me know if you need more information or wether i have missed to add some code!
Maybe I misunderstood you question but... IsActive property is not used for opening a tool saved into the layout. That property is used to set a Tool as active (focused). In order to open the tool saved into the layout you should handle the layoutSerializer_LayoutSerializationCallback attached the
Something like this:
var layoutSerializer = new XmlLayoutSerializer(this.DockManager);
layoutSerializer.LayoutSerializationCallback += layoutSerializer_LayoutSerializationCallback;
protected virtual void layoutSerializer_LayoutSerializationCallback(object sender, LayoutSerializationCallbackEventArgs e)
{
try
{
var model = this.Docs.Union(this.Tools).FirstOrDefault(vm => vm.ContentId == e.Model.ContentId);
if (model != null)
{
e.Content = model;
}
else
{
// Log load layout error info
}
}
catch (Exception ex)
{
// Log load layout error info
}
}
So I originally was going to just throw marker classes on the canvas manually in code behind, but then I thought, hmm, I shouldn't really be throwing actual viewobjects into the code inside my view model. So then I thought, "Let me just have a collection and then add to it when i want the View to update."
However, I'm having issues. The problem I am having is that the line I wish to display is not appearing at the desired location.
My goal was to have a small thumb control that I had customized with a template that I could then place in a canvas and use the canvas.setLeft(double) function to set the position of the thumb. This worked fine when I placed it inside a canvas. However, as soon as I switched to the following itemscontrol, instead of displaying the line at the desired location, it always displays the line at coordinate x = 0.
When I call canvas.getleft() on the thumb control, it returns the desired coordinate. However, that is not where it is displaying. I have created a test case to narrow down outside factors and figure out what is going on.
<Application.Resources>
<Style x:Key="CanvasMarkerStyle" TargetType="{x:Type Comp:CanvasMarker}">
<Setter Property="OverridesDefaultStyle" Value="true"/>
<Setter Property="StrokeThickness" Value="3"/>
<Setter Property="Width" Value="3"/>
<Setter Property="Height" Value="{Binding RelativeSource={RelativeSource AncestorType={x:Type Canvas}}, Path=ActualHeight}"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type Comp:CanvasMarker}">
<Line
Stroke="Orange"
StrokeThickness="{Binding RelativeSource={RelativeSource AncestorType={x:Type Comp:CanvasMarker}},
Path=StrokeThickness}"
X1="0"
Y1="0"
X2="0"
Y2="{Binding RelativeSource={RelativeSource AncestorType={x:Type Comp:CanvasMarker}}, Path=ActualHeight}"
/>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</Application.Resources>
and the Itemscontrol:
<ItemsControl
x:Name="ItemsControl"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
Grid.ZIndex="1"
Background="Gray"
>
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Canvas
x:Name="MarkerCanvas"
SnapsToDevicePixels="false"
/>
</ItemsPanelTemplate>
<Comp:CanvasMarker
MarkerTime="50"
/>
</DataTemplate>
On Loaded, I take MarkerTime and Canvas.SetLeft(this, MarkerTime);
I feel like there is an obvious reason why I cannot use canvas.setleft. My guess is that it has something to do with how canvas manages its attached properties. I am guessing that it uses them during layout to place the child correctly. I think that maybe in my case where the attached.left property is changed inside an itemspanel, it doesn't prompt another layout or something. Anyway, an explanation or suggestions would be greatly appreciated.
I figured doing it this way was much more MVVM than just creating view items on a canvas manually from my view model with the correct properties that i desired.
but having a control so I can bind an items collection seems to be more of a headache than i anticipated.
public CanvasMarker()
{
this.Unloaded += new RoutedEventHandler(CanvasMarker_Unloaded);
this.Initialized += new EventHandler(CanvasMarker_Initialized);
this.Loaded += new RoutedEventHandler(CanvasMarker_Loaded);
this.DragStarted += new DragStartedEventHandler(CanvasMarker_DragStarted);
this.DragDelta += new DragDeltaEventHandler(CanvasMarker_DragDelta);
this.PreviewMouseDown += new System.Windows.Input.MouseButtonEventHandler(CanvasMarker_PreviewMouseDown);
this.Style = (Style)Application.Current.Resources["CanvasMarkerStyle"];
}
void CanvasMarker_Loaded(object sender, RoutedEventArgs e)
{
gvvm = Tag as GraphViewerViewModel;
var p = VisualTreeUtilities.FindParent<DependencyObject>(this);
var gp = VisualTreeUtilities.FindParent<DependencyObject>(p);
var ggp = VisualTreeUtilities.FindParent<DependencyObject>(gp);
var gggp = VisualTreeUtilities.FindParent<DependencyObject>(ggp);
ParentCanvas = VisualTreeUtilities.FindParent<Canvas>(this) as Canvas;
double MarkerHorizontalPositionInPixels = MarkerTime / gvvm.UnitsOfTimePerPixel;
SetMarker(MarkerHorizontalPositionInPixels);
}
void CanvasMarker_Initialized(object sender, EventArgs e)
{
}
void CanvasMarker_Unloaded(object sender, RoutedEventArgs e)
{
}
static void SetMarkerToNewPosition(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
CanvasMarker cm = d as CanvasMarker;
if (cm.IsLoaded)
{
double MarkerHorizontalPositionInPixels = cm.MarkerTime / cm.gvvm.UnitsOfTimePerPixel;
cm.SetMarker(MarkerHorizontalPositionInPixels);
if (cm.IsCurrentMarker)
{
cm.gvvm.MarkerExists = true;
cm.gvvm.CalculateValuesAtPrimaryMarker();
}
}
}
void CanvasMarker_PreviewMouseDown(object sender, System.Windows.Input.MouseButtonEventArgs e)
{
}
void CanvasMarker_DragDelta(object sender, DragDeltaEventArgs e)
{
double NewXPosition = XPositionInPixels + e.HorizontalChange;
SetMarker(NewXPosition);
}
void SetMarker(double PositionInPixels)
{
if (PositionInPixels < StrokeThickness / 2)
{
PositionInPixels = StrokeThickness / 2;
}
else if (PositionInPixels > ParentCanvas.ActualWidth)
{
PositionInPixels = ParentCanvas.ActualWidth;
}
Canvas.SetLeft(this, PositionInPixels);
double d = Canvas.GetLeft(this);
XPositionInPixels = PositionInPixels;
}
void CanvasMarker_DragStarted(object sender, DragStartedEventArgs e)
{
}
Is there any way to achieve something like office undo drop down (image bellow) ?
I mean, i want to highlight previous item when user mouse over item other than first ?
I tried some control from FluentRibbon but so far without luck..
In most cases designing a control like this is done in Blend. However, if you don't know how to use Blend, you can still achieve the similar results with just XAML and the code-behind, but you have to do more work.
We start by creating a class called CustomListBoxItem which inherits from ListBoxItem. Then, we define a dependency property, which is used for highlighting items in the listbox:
public class CustomListBoxItem : ListBoxItem
{
public static readonly DependencyProperty IsVirtuallySelectedProperty =
DependencyProperty.Register("IsVirtuallySelected", typeof(bool),
typeof(CustomListBoxItem),
new PropertyMetadata(false));
public CustomListBoxItem() : base()
{ }
public bool IsVirtuallySelected
{
get { return (bool)GetValue(IsVirtuallySelectedProperty); }
set { SetValue(IsVirtuallySelectedProperty, value); }
}
}
Then we add a listbox and define a style for it in XAML:
<ListBox Name="listBox" MouseMove="listBox_MouseMove" SelectionChanged="listBox_SelectionChanged">
<ListBox.ItemContainerStyle>
<Style TargetType="{x:Type local:CustomListBoxItem}">
<Style.Triggers>
<Trigger Property="IsVirtuallySelected" Value="true">
<Setter Property="Background" Value="SkyBlue"/>
</Trigger>
<Trigger Property="IsVirtuallySelected" Value="false">
<Setter Property="Background" Value="White"/>
</Trigger>
</Style.Triggers>
</Style>
</ListBox.ItemContainerStyle>
</ListBox>
where local is the namespace in which CustomListBoxItem is defined. This is all we need for the XAML part, the real magic happens in the code behind.
The listBox_MouseMove event handler looks like this:
private void listBox_MouseMove(object sender, MouseEventArgs e)
{
bool itemFound = false;
for (int i = 0; i < listBox.Items.Count; i++)
{
var currentItem = listBox.ItemContainerGenerator.ContainerFromIndex(i) as CustomListBoxItem;
if (currentItem == null)
continue;
// Check whether the cursor is on an item or not.
if (IsMouseOverItem(currentItem, e.GetPosition((IInputElement)currentItem)))
{
// Unselect all items before selecting the new group
listBox.Items.Cast<CustomListBoxItem>().ToList().ForEach(x => x.IsVirtuallySelected = false);
// Select the current item and the ones above it
for (int j = 0; j <= listBox.Items.IndexOf(currentItem); j++)
{
((CustomListBoxItem)listBox.Items[j]).IsVirtuallySelected = true;
}
itemFound = true;
break;
}
}
// If the item wasn't found for the mouse point, it means the pointer is not over any item, so unselect all.
if (!itemFound)
{
listBox.Items.Cast<CustomListBoxItem>().ToList().ForEach(x => x.IsVirtuallySelected = false);
}
}
And the IsMouseOverItem helper method, which is used to determine if the cursor is on an item, is defined like this:
private bool IsMouseOverItem(Visual item, Point mouseOverPoint)
{
Rect currentDescendantBounds = VisualTreeHelper.GetDescendantBounds(item);
return currentDescendantBounds.Contains(mouseOverPoint);
}
And finally the listBox_SelectedChanged event handler which acts as the click handler for the ListBox:
private void listBox_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
// Get all the virtually selected items
List<CustomListBoxItem> selectedItems =
listBox.Items.Cast<CustomListBoxItem>().Where(x => x.IsVirtuallySelected).ToList();
if (selectedItems == null || !selectedItems.Any())
return;
// Do something with the selected items
DoCoolStuffWithSelectedItems();
// Unselsect all.
listBox.Items.Cast<CustomListBoxItem>().ToList().ForEach(x => x.IsVirtuallySelected = false);
listBox.UnselectAll();
}
And boom, we're done. We can now add some items to the ListBox in the class constructor:
public MainWindow()
{
InitializeComponent();
listBox.Items.Add(new CustomListBoxItem { Content = "hello world!" });
listBox.Items.Add(new CustomListBoxItem { Content = "wpf is cool" });
listBox.Items.Add(new CustomListBoxItem { Content = "today is tuesday..." });
listBox.Items.Add(new CustomListBoxItem { Content = "I like coffee" });
}
Note that I used a random color as the highlight color in XAML. Feel free to change it and give it a try.
Guess you need something like this:
<ControlTemplate TargetType="ListBoxItem">
<TextBlock Text="{Binding LastOperation}">
<TextBlock.Style>
<Style TargetType="TextBlock">
<Style.Triggers>
<DataTrigger>
<DataTrigger.Binding>
<MultiBinding>
<Binding Path="MouseOverIndex"/>
<Binding Path="CurrentIndex"/>
</MultiBinding>
</DataTrigger.Binding>
<Setter Property="Foreground" Value="Gold"/>
</DataTrigger>
</Style.Triggers>
</Style>
</TextBlock.Style>
</TextBlock>
</ControlTemplate>
I have one TextBlock having width say 100. When the text length is a large one I want to show the characters that is accomodated in that textblock and a (...) button besides the text to specify user that more text is also there. Upon click on that (...) button, the full text will be shown in a separate pop up window.
So i want how the dynamic (...) button will be shown whenever the text length exceed the size of the textblock. Please answer
This isn't exactly what you want, but it's a similar idea and just uses the baked-in stuff:
<TextBlock MaxWidth="200"
Text="{Binding YourLongText}"
TextTrimming="WordEllipsis"
ToolTip="{Binding YourLongText}" />
So you have a TextBlock with a maximum width, and when the text can't fit it displays an ellipsis ("..."). Hovering over the TextBlock with your mouse will show the full text in a ToolTip.
Just experience the same requirement for adding ellipsis on button so adding the solution here
<Style x:Key="editButton" TargetType="{x:Type Button}">
<Setter Property="Background" Value="Transparent" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type Button}">
<Border Background="{TemplateBinding Background}">
<ContentPresenter HorizontalAlignment="Left" VerticalAlignment="Center" >
<ContentPresenter.Resources>
<Style TargetType="TextBlock">
<Setter Property="TextTrimming" Value="CharacterEllipsis"></Setter>
</Style>
</ContentPresenter.Resources>
</ContentPresenter>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
<Style.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter Property="Background" Value="Transparent"/>
</Trigger>
</Style.Triggers>
</Style>
Notice the resources in content presenter.
I believe what you want is to set the TextTrimming property. Settng it to WordElilipsis or CharacterEllipsis should provide what you need.
My solution to the problem is probably overkill, but allows for some configuration and control.
I created a behavior that allows me to set the character limit for each binding.
internal class EllipsisStringBehavior
{
public static readonly DependencyProperty CharacterLimitDependencyProperty = DependencyProperty.RegisterAttached("CharacterLimit", typeof(int), typeof(EllipsisStringBehavior), new PropertyMetadata(255, null, OnCoerceCharacterLimit));
public static readonly DependencyProperty InputTextDependencyProperty = DependencyProperty.RegisterAttached("InputText", typeof(string), typeof(EllipsisStringBehavior), new PropertyMetadata(string.Empty, OnInputTextChanged));
// Input Text
public static string GetInputText(DependencyObject dependencyObject)
{
return Convert.ToString(dependencyObject.GetValue(InputTextDependencyProperty));
}
public static void SetInputText(DependencyObject dependencyObject, string inputText)
{
dependencyObject.SetValue(InputTextDependencyProperty, inputText);
}
// Character Limit
public static int GetCharacterLimit(DependencyObject dependencyObject)
{
return Convert.ToInt32(dependencyObject.GetValue(CharacterLimitDependencyProperty));
}
public static void SetCharacterLimit(DependencyObject dependencyObject, object characterLimit)
{
dependencyObject.SetValue(CharacterLimitDependencyProperty, characterLimit);
}
private static void OnInputTextChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
TextBlock textblock = (TextBlock)d;
string input = e.NewValue == null ? string.Empty : e.NewValue.ToString();
int limit = GetCharacterLimit(d);
string result = input;
if (input.Length > limit && input.Length != 0)
{
result = $"{input.Substring(0, limit)}...";
}
textblock.Text = result;
}
private static object OnCoerceCharacterLimit(DependencyObject d, object baseValue)
{
return baseValue;
}
}
I then simply add the using to my user control...
<UserControl
xmlns:behavior="clr-namespace:My_APP.Helper.Behavior"
d:DesignHeight="300" d:DesignWidth="300">
...and apply the behavior to the TextBlock control I wish to use it on.
<TextBlock Margin="0,8,0,8"
behavior:EllipsisStringBehavior.CharacterLimit="10"
behavior:EllipsisStringBehavior.InputText="{Binding Path=DataContext.FeedItemTwo.Body, Mode=OneWay, UpdateSourceTrigger=PropertyChanged}"
Style="{StaticResource MaterialDesignSubheadingTextBlock}"
FontSize="14"/>
Hope this helps.