在不缩放滚动条的情况下放大 ListView 内容
Zoom into ListView contents without also scaling scroll bars
我在 ListView
中有一个 GridView
。我想对内容添加 Ctrl+MWheelUp 缩放。
我已经使用下面的代码使用 ScaleTransform
实现了缩放部分,但是,由于它作为一个整体应用于 ListView
,它也会缩放滚动条。理想情况下,我希望滚动条保持固定大小(尽管显然会根据内部内容的变化进行调整)——但是,我不确定如何实现这一点。唯一的方法是将 ScaleTransform
应用于每个 GridViewColumn
的每个子节点,还是有另一种方法可以将其应用于整个 ListView
,而无需缩放滚动条?
我的(简体)xaml:
<ListView ScrollViewer.HorizontalScrollBarVisibility="Auto"
ScrollViewer.VerticalScrollBarVisibility="Auto"
x:Name="listView">
<ListView.View>
<GridView>
<GridViewColumn>...</GridViewColumn>
<GridViewColumn>...</GridViewColumn>
</GridView>
</ListView.View>
</ListView>
xaml.cs:
public Control()
{
InitializeComponent();
var mouseWheelZoom = new MouseWheelZoom(listView);
PreviewMouseWheel += mouseWheelZoom.Zoom;
}
鼠标滚轮缩放
public class MouseWheelZoom
{
private readonly FrameworkElement _element;
private double _currentZoomFactor;
public MouseWheelZoom(FrameworkElement element)
{
_element = element;
_currentZoomFactor = 1.0;
}
public void Zoom(object sender, MouseWheelEventArgs e)
{
var handle = (Keyboard.Modifiers & ModifierKeys.Control) > 0;
if (!handle)
return;
ApplyZoom(e.Delta);
}
private void ApplyZoom(int delta)
{
var zoomScale = delta / 500.0;
var newZoomFactor = _currentZoomFactor += zoomScale;
_element.LayoutTransform = new ScaleTransform(newZoomFactor, newZoomFactor);
_currentZoomFactor = newZoomFactor;
}
}
您可以使用总是方便的 FindChild<T>
function 检索 ListView
中的 ScrollContentPresenter
,并使用它的缩放功能。
public Control()
{
InitializeComponent();
this.Loaded += new RoutedEventHandler(Control_Loaded);
}
private void Control_Loaded(object sender, RoutedEventArgs e)
{
var presenter = FindChild<ScrollContentPresenter>(listView, null);
var mouseWheelZoom = new MouseWheelZoom(presenter);
PreviewMouseWheel += mouseWheelZoom.Zoom;
}
请注意,我已将代码放入 Loaded 事件处理程序中。那是因为 ScrollContentPresenter
是 ListView
模板的一部分,而不是视图的直接部分,因此在控件完全加载其样式和模板之前它不会存在。
PD.: 另外值得注意的是 ListView
的其他部分,如 headers 等,不会被缩放。只有项目会。
没试过,不过你可以试试
<ListBox.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel /> <!-- scale this, it's inside ScrollViewer -->
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
我刚刚创建了一个附件 属性,它似乎产生了您正在寻找的效果。这是 属性.
的代码
internal static class ListViewBehaviors
{
public static readonly DependencyProperty ContentTransformProperty = DependencyProperty.RegisterAttached("ContentTransform", typeof(Transform), typeof(ListViewBehaviors),
new PropertyMetadata((Transform)null, OnContentTransformChanged));
public static Transform GetContentTransform(ListView obj)
{
return (Transform)obj.GetValue(ContentTransformProperty);
}
public static void SetContentTransform(ListView obj, Transform value)
{
obj.SetValue(ContentTransformProperty, value);
}
private static void OnContentTransformChanged(DependencyObject obj, DependencyPropertyChangedEventArgs e)
{
ListView view = obj as ListView;
if (view != null)
{
if (view.ItemContainerGenerator.Status != GeneratorStatus.ContainersGenerated)
{
EventHandler handler = null;
handler = (s, a) => ListView_ItemContainerGenerator_StatusChanged(view, handler);
view.ItemContainerGenerator.StatusChanged += handler;
}
else
{
UpdateTransform(view);
}
}
}
private static void ListView_ItemContainerGenerator_StatusChanged(ListView view, EventHandler handler)
{
if (view.ItemContainerGenerator.Status == GeneratorStatus.ContainersGenerated)
{
view.ItemContainerGenerator.StatusChanged -= handler;
UpdateTransform(view);
}
}
private static void UpdateTransform(ListView view)
{
if (view.IsArrangeValid)
{
DoUpdateTransform(view);
}
else
{
EventHandler handler = null;
handler = (s, e) => LayoutUpdated(view, handler);
view.LayoutUpdated += handler;
}
}
private static void LayoutUpdated(ListView view, EventHandler handler)
{
view.LayoutUpdated -= handler;
DoUpdateTransform(view);
}
private static void DoUpdateTransform(ListView view)
{
ScrollViewer scroller = VisualTreeUtility.FindDescendant<ScrollViewer>(view);
if (scroller != null)
{
Transform transform = GetContentTransform(view);
FrameworkElement header = VisualTreeUtility.FindDescendant<ScrollViewer>(scroller);
if (header != null)
{
header.LayoutTransform = transform;
}
FrameworkElement content = scroller.Template.FindName("PART_ScrollContentPresenter", scroller) as FrameworkElement;
if (content != null)
{
content.LayoutTransform = transform;
}
}
}
}
此外,这里是 VisualTreeUtility.FindDescendant
方法的代码。
public static class VisualTreeUtility
{
public static T FindDescendant<T>(DependencyObject ancestor) where T : DependencyObject
{
return FindDescendant<T>(ancestor, item => true);
}
public static T FindDescendant<T>(DependencyObject ancestor, Predicate<T> predicate) where T : DependencyObject
{
return FindDescendant(typeof(T), ancestor, item => predicate((T)item)) as T;
}
public static DependencyObject FindDescendant(Type itemType, DependencyObject ancestor, Predicate<DependencyObject> predicate)
{
if (itemType == null) throw new ArgumentNullException("itemType");
if (ancestor == null) throw new ArgumentNullException("ancestor");
if (predicate == null) throw new ArgumentNullException("predicate");
if (!typeof(DependencyObject).IsAssignableFrom(itemType)) throw new ArgumentException("itemType", "The passed in type must be or extend DependencyObject");
Queue<DependencyObject> queue = new Queue<DependencyObject>();
queue.Enqueue(ancestor);
while (queue.Count > 0)
{
DependencyObject currentChild = queue.Dequeue();
if (currentChild != ancestor && itemType.IsAssignableFrom(currentChild.GetType()))
{
if(predicate.Invoke(currentChild))
{
return currentChild;
}
}
int count = VisualTreeHelper.GetChildrenCount(currentChild);
for (int i = 0; i < count; ++i)
{
queue.Enqueue(VisualTreeHelper.GetChild(currentChild, i));
}
}
return null;
}
}
下面是如何使用 属性:
<ListView
ItemsSource="{Binding Items}">
<local:ListViewBehaviors.ContentTransform>
<ScaleTransform ScaleX="2" ScaleY="2" />
</local:ListViewBehaviors.ContentTransform>
<ListView.View>
...
</ListView.View>
</ListView>
如果有任何遗漏或混淆,请告诉我。
我在 ListView
中有一个 GridView
。我想对内容添加 Ctrl+MWheelUp 缩放。
我已经使用下面的代码使用 ScaleTransform
实现了缩放部分,但是,由于它作为一个整体应用于 ListView
,它也会缩放滚动条。理想情况下,我希望滚动条保持固定大小(尽管显然会根据内部内容的变化进行调整)——但是,我不确定如何实现这一点。唯一的方法是将 ScaleTransform
应用于每个 GridViewColumn
的每个子节点,还是有另一种方法可以将其应用于整个 ListView
,而无需缩放滚动条?
我的(简体)xaml:
<ListView ScrollViewer.HorizontalScrollBarVisibility="Auto"
ScrollViewer.VerticalScrollBarVisibility="Auto"
x:Name="listView">
<ListView.View>
<GridView>
<GridViewColumn>...</GridViewColumn>
<GridViewColumn>...</GridViewColumn>
</GridView>
</ListView.View>
</ListView>
xaml.cs:
public Control()
{
InitializeComponent();
var mouseWheelZoom = new MouseWheelZoom(listView);
PreviewMouseWheel += mouseWheelZoom.Zoom;
}
鼠标滚轮缩放
public class MouseWheelZoom
{
private readonly FrameworkElement _element;
private double _currentZoomFactor;
public MouseWheelZoom(FrameworkElement element)
{
_element = element;
_currentZoomFactor = 1.0;
}
public void Zoom(object sender, MouseWheelEventArgs e)
{
var handle = (Keyboard.Modifiers & ModifierKeys.Control) > 0;
if (!handle)
return;
ApplyZoom(e.Delta);
}
private void ApplyZoom(int delta)
{
var zoomScale = delta / 500.0;
var newZoomFactor = _currentZoomFactor += zoomScale;
_element.LayoutTransform = new ScaleTransform(newZoomFactor, newZoomFactor);
_currentZoomFactor = newZoomFactor;
}
}
您可以使用总是方便的 FindChild<T>
function 检索 ListView
中的 ScrollContentPresenter
,并使用它的缩放功能。
public Control()
{
InitializeComponent();
this.Loaded += new RoutedEventHandler(Control_Loaded);
}
private void Control_Loaded(object sender, RoutedEventArgs e)
{
var presenter = FindChild<ScrollContentPresenter>(listView, null);
var mouseWheelZoom = new MouseWheelZoom(presenter);
PreviewMouseWheel += mouseWheelZoom.Zoom;
}
请注意,我已将代码放入 Loaded 事件处理程序中。那是因为 ScrollContentPresenter
是 ListView
模板的一部分,而不是视图的直接部分,因此在控件完全加载其样式和模板之前它不会存在。
PD.: 另外值得注意的是 ListView
的其他部分,如 headers 等,不会被缩放。只有项目会。
没试过,不过你可以试试
<ListBox.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel /> <!-- scale this, it's inside ScrollViewer -->
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
我刚刚创建了一个附件 属性,它似乎产生了您正在寻找的效果。这是 属性.
的代码internal static class ListViewBehaviors
{
public static readonly DependencyProperty ContentTransformProperty = DependencyProperty.RegisterAttached("ContentTransform", typeof(Transform), typeof(ListViewBehaviors),
new PropertyMetadata((Transform)null, OnContentTransformChanged));
public static Transform GetContentTransform(ListView obj)
{
return (Transform)obj.GetValue(ContentTransformProperty);
}
public static void SetContentTransform(ListView obj, Transform value)
{
obj.SetValue(ContentTransformProperty, value);
}
private static void OnContentTransformChanged(DependencyObject obj, DependencyPropertyChangedEventArgs e)
{
ListView view = obj as ListView;
if (view != null)
{
if (view.ItemContainerGenerator.Status != GeneratorStatus.ContainersGenerated)
{
EventHandler handler = null;
handler = (s, a) => ListView_ItemContainerGenerator_StatusChanged(view, handler);
view.ItemContainerGenerator.StatusChanged += handler;
}
else
{
UpdateTransform(view);
}
}
}
private static void ListView_ItemContainerGenerator_StatusChanged(ListView view, EventHandler handler)
{
if (view.ItemContainerGenerator.Status == GeneratorStatus.ContainersGenerated)
{
view.ItemContainerGenerator.StatusChanged -= handler;
UpdateTransform(view);
}
}
private static void UpdateTransform(ListView view)
{
if (view.IsArrangeValid)
{
DoUpdateTransform(view);
}
else
{
EventHandler handler = null;
handler = (s, e) => LayoutUpdated(view, handler);
view.LayoutUpdated += handler;
}
}
private static void LayoutUpdated(ListView view, EventHandler handler)
{
view.LayoutUpdated -= handler;
DoUpdateTransform(view);
}
private static void DoUpdateTransform(ListView view)
{
ScrollViewer scroller = VisualTreeUtility.FindDescendant<ScrollViewer>(view);
if (scroller != null)
{
Transform transform = GetContentTransform(view);
FrameworkElement header = VisualTreeUtility.FindDescendant<ScrollViewer>(scroller);
if (header != null)
{
header.LayoutTransform = transform;
}
FrameworkElement content = scroller.Template.FindName("PART_ScrollContentPresenter", scroller) as FrameworkElement;
if (content != null)
{
content.LayoutTransform = transform;
}
}
}
}
此外,这里是 VisualTreeUtility.FindDescendant
方法的代码。
public static class VisualTreeUtility
{
public static T FindDescendant<T>(DependencyObject ancestor) where T : DependencyObject
{
return FindDescendant<T>(ancestor, item => true);
}
public static T FindDescendant<T>(DependencyObject ancestor, Predicate<T> predicate) where T : DependencyObject
{
return FindDescendant(typeof(T), ancestor, item => predicate((T)item)) as T;
}
public static DependencyObject FindDescendant(Type itemType, DependencyObject ancestor, Predicate<DependencyObject> predicate)
{
if (itemType == null) throw new ArgumentNullException("itemType");
if (ancestor == null) throw new ArgumentNullException("ancestor");
if (predicate == null) throw new ArgumentNullException("predicate");
if (!typeof(DependencyObject).IsAssignableFrom(itemType)) throw new ArgumentException("itemType", "The passed in type must be or extend DependencyObject");
Queue<DependencyObject> queue = new Queue<DependencyObject>();
queue.Enqueue(ancestor);
while (queue.Count > 0)
{
DependencyObject currentChild = queue.Dequeue();
if (currentChild != ancestor && itemType.IsAssignableFrom(currentChild.GetType()))
{
if(predicate.Invoke(currentChild))
{
return currentChild;
}
}
int count = VisualTreeHelper.GetChildrenCount(currentChild);
for (int i = 0; i < count; ++i)
{
queue.Enqueue(VisualTreeHelper.GetChild(currentChild, i));
}
}
return null;
}
}
下面是如何使用 属性:
<ListView
ItemsSource="{Binding Items}">
<local:ListViewBehaviors.ContentTransform>
<ScaleTransform ScaleX="2" ScaleY="2" />
</local:ListViewBehaviors.ContentTransform>
<ListView.View>
...
</ListView.View>
</ListView>
如果有任何遗漏或混淆,请告诉我。