I'm trying to show a vertical line in position X of the cursor. I'm trying to do that in a slider control, but I couldn't made it. This will be showed while the user has its cursor on it.
I only know that I need to modify the template. Is so much difficult to do that? If not, can you help me?
Thanks.
This is not easy to attain using templating because of the fact that mouse position is not a dependency property and mouse move is not a routed event. This really comes down to what you want to do, if it's to just show a vertical line whether the mouse is then I agree with Dany to use adorners, as you're not really interested in the slider itself. However, if it's to move the thumb to where the mouse is I would use an attached property.
The reason I use attached properties rather than hooking up the events directly on the control is that it provides more modularised and reusable codebase and makes it more obvious of the visual effect in the XAML, rather than needing to look at C# code as well.
Here's what I would suggest
public class SliderHelperPackage
{
public static readonly DependencyProperty BindThumbToMouseProperty = DependencyProperty.RegisterAttached(
"BindThumbToMouse", typeof(bool), typeof(SliderHelperPackage), new PropertyMetadata(false, OnBindThumbToMouseChanged));
public static void SetBindThumbToMouse(UIElement element, bool value)
{
element.SetValue(BindThumbToMouseProperty, value);
}
public static bool GetBindThumbToMouse(UIElement element)
{
return (bool)element.GetValue(BindThumbToMouseProperty);
}
private static void OnBindThumbToMouseChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
if (e.NewValue.Equals(e.OldValue))
return;
var slider = d as Slider;
if (slider == null)
throw new ArgumentException(#"dependency object must be a slider", "d");
if ((bool) e.NewValue)
{
slider.MouseMove += SliderMouseMove;
}
else
{
slider.MouseMove -= SliderMouseMove;
}
}
static void SliderMouseMove(object sender, MouseEventArgs e)
{
var slider = (Slider) sender;
var position = e.GetPosition(slider);
// When the mouse moves, we update the slider's value so it's where the ticker is (we take into account margin's on the track)
slider.Value = (slider.Maximum - slider.Minimum)*Math.Max(position.X-5,0)/(slider.ActualWidth-10) + slider.Minimum;
}
}
And you can then use it in your XAML like so. So when you set this to true we hook up to the mouse move event on the slider and change its value so the thumb follows the mouse
<Slider SliderPosn:SliderHelperPackage.BindThumbToMouse="True" Margin="5" Height="25" VerticalAlignment="Top"/>
http://www.codeproject.com/KB/WPF/adornedcontrol.aspx
this reference should give you all necessary information to fix your issue
cheers!
Related
I'm trying to get WPF Slider value after the user finishing to drag the thumb or clicking to move to specific point.
i want to save the new value in db by listening to some kind of event. how can i do this?
i tried the solutions on this question but i end up with nothing -
WPF: Slider with an event that triggers after a user drags
each time it enters the event lots of times
thanks.
xaml:
<Slider Value="{Binding VoipVolume}" MouseLeftButtonUp="slider_MouseLeftButtonUp"/>
codebehind:
public double VoipVolume
{
get { return (double)GetValue(VoipVolumeProperty); }
set
{
SetValue(VoipVolumeProperty, value);
VolumeChanged(value);
}
}
private void VolumeChanged(object value)
{
StationViewModel viewModel = this.DataContext as StationViewModel;
if (viewModel != null)
{
if (end)
{
viewModel.OnVolumeChange((float)VoipVolume);
end = false;
}
}
}
bool end = false;
private void slider_MouseLeftButtonUp(object sender, System.Windows.Input.MouseButtonEventArgs e)
{
end = true;
}
Possibly duplicate. hope this may help you out: stackoverflow.com/a/723547/1611490
<Slider Thumb.DragCompleted="MySlider_DragCompleted" />
and under MySlider_DragCompleted event, update your property of the VM or get the value.
if your are using FW 4.5 or above, try using the "Dalay" property.
http://www.jonathanantoine.com/2011/09/21/wpf-4-5-part-4-the-new-bindings-delay-property/
We didn't have a choice, in the end we use TickFrequency because we need to save the changes in the slider to db, and we don't want it to happen for every 0.001..
thanks anyway..
I have a simple ListView in a Universal Windows App. It scrolls just fine both on a touch-enabled device and on my local machine. On the touch device I would like to be able to tap and drag the scroll bar thumb in order to quickly scroll through the list (same as you would get clicking and dragging the scroll thumb with a mouse). But, when I try to select the scroll bar thumb it does not work; the thumb is not selectable.
At first I thought the scroll thumb was just too small, so I fiddled with the default style of ScrollBar to increase the width. Then I tried tweaking other values in the default ScrollViewer style, like the ScrollingIndicatorMode and ScrollingIndicatorStates so that they would always use MouseIndicator, and making sure all IsHitTestVisible are True. To no avail.
It seems like there must be something in the default styles that allows this but I can't find it through trial and error, and no where in the MSDN docs appears to address this styling.
Is this doable in touch mode?
Is this doable in touch mode?
AFAIK, this is not possible currently. The scroll event of ScrollBar can only be triggered by scrolling the content or moving the Thumb by mouse input.
This is by design, but on the touch device if we want to quickly scroll through the list, we just quickly swipe one item for a short distance, the ListView will firstly speed up the scrolling and then slow down and finally stop. Other controls which contain a ScrollViewer behave the same, in the viewport(content of ScrollViewer) when in touch device, moving the thumb is not work, to speed up the scrolling, you can only give quick swipe gesture on the viewport.
Our suggestion is that you may submit a request to add this new features for developing through the Windows Feedback tool.
For posterity's sake I want to post my work around which actually turned out not being quite as difficult as I thought and looks really good now.
The basic idea is to use a Slider control (has a Thumb that is select-able, drag-able, and moves along a track) with a vertical orientation and to sync the Slider's Value property (which changes when dragged) to the ListView's ScrollIntoView method so you can scroll the ListView as the Slider Thumb is dragged. Vice-versa (move the Slider Thumb when the ListView scrolls normally) is also required for a clean, seamless experience. I have some code sample below to get you started (not everything is there to work right away). In addition, edit the Slider's default Style template to make it look exactly like a scroll bar (and even even add a tooltip to display the current value when dragging).
Note: I'm using WinRTXamlToolkit in code-behind to traverse the VisualTree easily.
Code behind:
public sealed partial class MainPage : Page
{
private bool _isScroll = false;
private bool _isSlide = false;
public MainPage()
{
this.InitializeComponent();
var vm = new ViewModel();
vm.ValueChanged += LetterSliderValueChanged;
DataContext = vm;
}
/// <summary>
/// Bring list items into view on the screen based on the value (letter) of the slider
/// </summary>
/// <param name="sender">The view model to bring into view</param>
private void LetterSliderValueChanged(object sender, RoutedEventArgs e)
{
if (_isScroll) return;
if (sender == null) return;
_isSlide = true;
ListView?.ScrollIntoView(sender, ScrollIntoViewAlignment.Leading);
}
/// <summary>
/// Update the position of the slider when the ListView is scrolling from a normal touch
/// </summary>
private void ScrollViewerViewChanged(object sender, ScrollViewerViewChangedEventArgs e)
{
if (_isSlide)
{
_isSlide = false;
return;
}
_isScroll = true;
var scrollViewer = sender as ScrollViewer;
var scrollBars = scrollViewer.GetDescendantsOfType<ScrollBar>();
var verticalBar = scrollBars.FirstOrDefault(x => x.Orientation == Orientation.Vertical);
// Normalize the scales to move the slider thumb in sync with scrolling
var sliderTotal = LetterSlider.Maximum - LetterSlider.Minimum;
var barTotal = verticalBar.Maximum - verticalBar.Minimum;
var barPercent = verticalBar.Value / barTotal;
LetterSlider.Value = (barPercent * sliderTotal) + LetterSlider.Minimum;
_isScroll = false;
}
/// <summary>
/// Add the slider method to the ListView's ScrollViewer Viewchanged event
/// </summary>
private void ListViewLoaded(object sender, RoutedEventArgs e)
{
var listview = sender as ListView;
if (listview == null) return;
var scrollViewer = listview.GetFirstDescendantOfType<ScrollViewer>();
scrollViewer.ViewChanged -= ScrollViewerViewChanged;
scrollViewer.ViewChanged += ScrollViewerViewChanged;
}
}
XAML:
<Slider x:Name="LetterSlider" Orientation="Vertical"
Value="{Binding SliderValue, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
Maximum="{Binding MaxSliderLetter, Mode=OneTime}"
Minimum="{Binding MinSliderLetter, Mode=OneTime}"
RenderTransformOrigin="0.5,0.5">
<Slider.RenderTransform>
<RotateTransform Angle="180"/>
</Slider.RenderTransform>
</Slider>
View Model:
//Method to update the ListView to show items based on the letter of the slider
public RoutedEventHandler ValueChanged;
public int SliderValue
{
get { return _sliderValue; }
set
{
_sliderValue = value;
NotifyPropertyChanged("SliderValue");
char letter = (char)_sliderValue;
var items = ItemsGroup.FirstOrDefault(i => (char)i.Key == letter);
if (items == null) return;
ValueChanged(items.FirstOrDefault(), new RoutedEventArgs());
}
}
I'm trying to setting the focus for a certain UIElement dependency object from viewmodel and to do that I have written an Attached Dependency Property as below:
#region IsFocusedProperty
public static readonly DependencyProperty IsFocusedProperty =
DependencyProperty.RegisterAttached("IsFocused", typeof(bool), typeof(AttachedProperties),
new FrameworkPropertyMetadata(false,
new PropertyChangedCallback(OnIsFocusedChanged)));
private static void OnIsFocusedChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var obj = d as UIElement;
var oldValue = (bool)e.OldValue;
var newValue = (bool)e.NewValue;
if (oldValue != newValue && !obj.IsFocused)
{
obj.Focus();
}
}
public static void SetIsFoused(UIElement element, bool value)
{
element.SetValue(IsFocusedProperty, value);
}
public static bool GetIsFocused(UIElement element)
{
return (bool)element.GetValue(IsFocusedProperty);
}
#endregion
I should say it works and sends the focus to the specified element unless when I move the mouse cursor over each element in the view (including the specified element on which the focus is) no mouse event fires. I'm saying this because I've written triggers for IsMouseOverProperty of the elements but nothings happen when the focus is sent to the specified element in that way.
I should also say that the triggers works fine again when I click anywhere on the view. I really don't know what the problem is? please share your ideas. any idea would be appreciated. thanks in advance.
Edit:
I've figured it out that the element receives the keyboard focus but apparently not the logical focus because when I move the cursor to the previous element, it still fires MouseMove event.
I'm still not sure what the problem is?
Update to:
if (oldValue != newValue && !obj.IsFocused)
{
Dispatcher.BeginInvoke(DispatcherPriority.Input,
new Action(delegate()
{
obj.Focus();
Keyboard.Focus(obj);
})
);
}
I'm trying to achieve an effect where more items are appended to the list when the user scrolls down to the last item. I haven't found a way to determine if the user has scrolled to the end of the list. I don't see a event on ListBox that is fired when the user reaches the bottom of the list. Something that tells me when an item has been scrolled into view would be great, but as far as I can tell, there is nothing like that.
Is this even possible in WP7?
Edit: Another way of saying this is, can we detect when a list has "bounced"?
Daniel Vaughan has posted an example of how to detect for this at http://danielvaughan.orpius.com/post/Scroll-Based-Data-Loading-in-Windows-Phone-7.aspx
It isn't super easy to get going since there are a lot of moving parts, but here is what you can do, assuming you want a short list that loads more from your data as you get scrolling down, similar to a lot of twitter apps, etc.
Write your own subclass of ObservableCollection that only offers up a few items (like 20), keeping the rest held back until requested
Hook up to the scroll viewer (inside the listbox or container) and its visual state changed events, you can get the NotScrolling and Scrolling changes; for an example see this code by ptorr
When scrolling stops, use viewer scroll extensions code to see where things are extended (at the bottom or not) or just the raw scroll viewer properties to see if it is extended to the bottom
If so, trigger your observable collection to release another set of items.
Sorry I don't have a complete sample ready to blog yet. Good luck!
I've just implemented this for Overflow7.
The approach I took was similar to http://blog.slimcode.com/2010/09/11/detect-when-a-listbox-scrolls-to-its-end-wp7/
However, instead of using a Style I did the hook up in code.
Basically derived my parent UserControl from:
public class BaseExtendedListUserControl : UserControl
{
DependencyProperty ListVerticalOffsetProperty = DependencyProperty.Register(
"ListVerticalOffset",
typeof(double),
typeof(BaseExtendedListUserControl),
new PropertyMetadata(new PropertyChangedCallback(OnListVerticalOffsetChanged)));
private ScrollViewer _listScrollViewer;
protected void EnsureBoundToScrollViewer()
{
if (_listScrollViewer != null)
return;
var elements = VisualTreeHelper.FindElementsInHostCoordinates(new Rect(0,0,this.Width, this.Height), this);
_listScrollViewer = elements.Where(x => x is ScrollViewer).FirstOrDefault() as ScrollViewer;
if (_listScrollViewer == null)
return;
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 static void OnListVerticalOffsetChanged(DependencyObject obj, DependencyPropertyChangedEventArgs e)
{
BaseExtendedListUserControl control = obj as BaseExtendedListUserControl;
control.OnListVerticalOffsetChanged();
}
private void OnListVerticalOffsetChanged()
{
OnListVerticalOffsetChanged(_listScrollViewer);
}
protected virtual void OnListVerticalOffsetChanged(ScrollViewer s)
{
// do nothing
}
}
this then meant that in the user control itself I could just use:
protected override void OnListVerticalOffsetChanged(ScrollViewer viewer)
{
// Trigger when at the end of the viewport
if (viewer.VerticalOffset >= viewer.ScrollableHeight)
{
if (MoreClick != null)
{
MoreClick(this, new RoutedEventArgs());
}
}
}
private void ListBox1_ManipulationCompleted(object sender, ManipulationCompletedEventArgs e)
{
EnsureBoundToScrollViewer();
}
The "hacky" thing here was that I had to use ListBox1_ManipulationCompleted and VisualTreeHelper to find my ScrollViewer - I'm sure there are better ways...
Have a look at this detect Listbox compression state from msdn blog
Use the DeferredLoadListBox.
enter code hereI have a ScrollViewer in Silverlight that is not scrolling vertically whenever I call the ScrollToVerticalOffset method from the code behind.
Basically, I have my View (UserControl) that contains the ScrollViewer. I invoke an action from my ViewModel that triggers an event in the View's code-behind that sets the VerticalOffset to a specific value.
First of all, I know this is very ugly. Ideally I wish that I could have an attachable property that I could bind to a property in my ViewModel, that, when set, would cause the VerticalOffset property (which I know is read-only) to be updated, and the ScrollViewer to scroll.
The ScrollViewer contains dynamic content. So, if the user is viewing content in the ScrollViewer, and scrolls half-way down, and then clicks on a button, new content is loaded into the ScrollViewer. The problem is that the ScrollViewer's vertical offset doesn't get reset, so the user has to scroll up to read the content. So, my solution was to be able to control the vertical offset from the ViewModel, and I have racked my brain and can't come up with a viable solution, so I am looking for someone to help, please.
By the way - I have included code from a class I put together for an attachable property. This property binds to a property in my ViewModel. When I set the property in the ViewModel, it correctly triggers the PropertyChanged callback method in this class, which then calls the ScrollToVerticalOffset method for the ScrollViewer, but the ScrollViewer still doesn't scroll.
public class ScrollViewerHelper
{
public static readonly DependencyProperty BindableOffsetProperty =
DependencyProperty.RegisterAttached("BindableOffset", typeof(double), typeof(ScrollViewerHelper),
new PropertyMetadata(OnBindableOffsetChanged));
public static double GetBindableOffset(DependencyObject d)
{
return (double)d.GetValue(BindableOffsetProperty);
}
public static void SetBindableOffset(DependencyObject d, double value)
{
d.SetValue(BindableOffsetProperty, value);
}
private static void OnBindableOffsetChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
ScrollViewer scrollViewer = d as ScrollViewer;
if (scrollViewer != null)
{
scrollViewer.ScrollToVerticalOffset((double)e.NewValue);
}
}
}
This approach is a little bit funky in my opinion, as I think of both a ScrollViewer and a VerticalScrollOffset as "View" entities that should have very little (or nothing) to do with a ViewModel. It seems like this might be forcing MVVM a little too much, and creating a lot of extra work in creating an attached dependency property and basically trying to keep a bound Offset ViewModel property in sync with the readonly VerticalScrollOffset of the ScrollViewer.
I am not exactly sure of what you are trying to achieve, but it sounds like you are trying to scroll to a specified offset when some dynamic element is added to the underlying panel of your ScrollViewer. Personally, I would just want to handle this behavior with a little bit of code in my View and forget about tying it to the ViewModel.
One really nice way to do this type of thing in Silverlight 3 is with Blend behaviors. You write a little bit of behavior code in C# and then can attach it declaratively to an element in XAML. This keeps it reusable and out of your code-behind. Your project will have to reference the System.Windows.Interactivity DLL which is part of the Blend SKD.
Here's a simple example of a simple Blend behavior you could add to a ScrollViewer which scrolls to a specified offset whenever the size of the underlying content of the ScrollViewer changes:
public class ScrollToOffsetBehavior : Behavior<ScrollViewer>
{
private FrameworkElement contentElement = null;
public static readonly DependencyProperty OffsetProperty = DependencyProperty.Register(
"Offset",
typeof(double),
typeof(ScrollToOffsetBehavior),
new PropertyMetadata(0.0));
public double Offset
{
get { return (double)GetValue(OffsetProperty); }
set { SetValue(OffsetProperty, value); }
}
protected override void OnAttached()
{
base.OnAttached();
if (this.AssociatedObject != null)
{
this.AssociatedObject.Loaded += new RoutedEventHandler(AssociatedObject_Loaded);
}
}
protected override void OnDetaching()
{
base.OnDetaching();
if (this.contentElement != null)
{
this.contentElement.SizeChanged -= contentElement_SizeChanged;
}
if (this.AssociatedObject != null)
{
this.AssociatedObject.Loaded -= AssociatedObject_Loaded;
}
}
void AssociatedObject_Loaded(object sender, RoutedEventArgs e)
{
this.contentElement = this.AssociatedObject.Content as FrameworkElement;
if (this.contentElement != null)
{
this.contentElement.SizeChanged += new SizeChangedEventHandler(contentElement_SizeChanged);
}
}
void contentElement_SizeChanged(object sender, SizeChangedEventArgs e)
{
this.AssociatedObject.ScrollToVerticalOffset(this.Offset);
}
}
You could then apply this behavior to the ScrollViewer in XAML (and specify an offset of 0 to scroll back to the top):
<ScrollViewer>
<i:Interaction.Behaviors>
<local:ScrollToOffsetBehavior Offset="0"/>
</i:Interaction.Behaviors>
...Scroll Viewer Content...
</ScrollViewer>
This would be assuming that you always want to scroll to the offset whenever the content size changes. This may not be exactly what you are looking for, but it is one example of how something like this can be done in the view using a behavior.