我正在尝试创建与ComboBoxes相关的Blend行为。为了获得我想要的效果,ComboBox的ItemsPanel必须添加一个元素。我不想在每个使用该行为的ComboBox中执行此操作,因此我希望Behavior能够以编程方式注入ItemsPanelTemplate。但是,我似乎找不到办法做到这一点。 ItemsPanelTemplate似乎没有允许我设置可视树的属性/方法。 WPF ItemsPanelTemplate具有VisualTree但Silverlight不具有。
基本上,这个XAML的程序化等价物是什么?
<ComboBox>
<ComboBox.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel/>
</ItemsPanelTemplate>
</ComboBox.ItemsPanel>
</ComboBox>
修改
好吧,显然这不是一个简单的问题,所以我开始了一个赏金,我将提供更多的背景,以防有另一种方式来解决这个问题。我想为Silverlight ComoboBox提供键盘支持。开箱即用它只支持向上和向下箭头,但我也希望它能够工作,这样当用户点击一个字母时,ComboBox会跳转到该字母的第一项,类似于ComboBoxes在浏览器或Windows应用程序中的工作方式
我找到了this blog post,这让我走了一半路。调整该行为代码,ComboBox将根据字母输入更改选择。 然而,它在打开ComboBox时不起作用。根据{{3}},其原因在于,当打开ComboBox时,您现在正在与其ItemsPanel进行交互,而不是ComboBox本身。所以根据那篇文章,我实际上需要将一个StackPanel添加到ItemsPanelTemplate并订阅StackPanel的KeyDown事件,以便在打开ComboBox时采取行动。
这就是我的问题,即如何将一个StackPanel放入ComboBox的ItemsPanelTemplate,来自行为。如果这是不可能的,有没有其他方法让这个工作?是的,我知道我可以去应用程序中的每个ComboBox并添加一个StackPanel和事件。但是我想通过一个行为来做到这一点,这样我就不必修改应用程序中的每个ComboBox,因此我可以跨应用程序重用这个逻辑。
下面使用XamlReader的AnthonyWJones回答让我有所帮助,因为我可以创建StackPanel并将其放入模板中。但是,我需要能够以编程方式获取该SP以订阅该事件。答案 0 :(得分:6)
这应该有效。我已经展示了如何改变下面的方向。您可以添加其他SetValue调用来修改其他属性。
cb.ItemsPanel = new ItemsPanelTemplate();
var stackPanelFactory = new FrameworkElementFactory(typeof (StackPanel));
// Modify it like this:
stackPanelFactory.SetValue(StackPanel.OrientationProperty, Orientation.Horizontal);
// Set the root of the template to the stack panel factory:
cb.ItemsPanel.VisualTree = stackPanelFactory;
您可以在本文中找到更多详细信息:http://www.codeproject.com/KB/WPF/codeVsXAML.aspx
答案 1 :(得分:4)
您实际想要以编程方式构建的内容是: -
<ItemsPanelTemplate>
<StackPanel />
</ItemsPanelTemplate>
然后,您的行为会将其分配给附加到的ItemsPanel
的{{1}}属性。目前您的行为是纯代码,但无法在代码中创建上述内容。
由于这是Xaml的一小部分,最简单的方法是使用XamlReader: -
ComboBox
答案 2 :(得分:1)
我认为,最好的方法 - 扩展组合框功能,而不是通过行为而是使用继承。 因此,您可以创建自己的控件MyComboBox:ComboBox。为它创建样式 - 获取默认的ComboBox样式 here
改为编写(按名称查找ScrollViewer):
&LT; ScrollViewer x:Name =“ScrollViewer”BorderThickness =“0”Padding =“1”&gt;
< ItemsPresenter />
&LT; / ScrollViewer&gt;
此
&LT; ScrollViewer x:Name =“ScrollViewer”BorderThickness =“0”Padding =“1”&gt;
< StackPanel x:Name="StackPanel" > < ItemsPresenter /> < /StackPanel >
&LT; / ScrollViewer&gt;
这个StackPanel你可以得到代码:
公共类MyComboBox:ComboBox {
public CM()
{
DefaultStyleKey = typeof (MyComboBox);
}
public override void OnApplyTemplate()
{
base.OnApplyTemplate();
StackPanel stackPanel = (StackPanel)GetTemplateChild("StackPanel");
stackPanel.KeyUp += (s, e) => { /*do something*/ };
}
}
继承更强大。它允许使用模板元素。 如果你决定注入ItemsPanel,你必须明白:
1)在注册面板上保留参考代码是不可能的 2)要获得对注入面板的引用,该面板必须在某些存储器中注册,例如
祝你好运!&LT;组合框&GT;
< ComboBox.ItemsPanel> < ItemsPanelTemplate> < StackPanel> < i:Interaction.EventTriggers> < i:EventTrigger EventName="Loaded"> < RegisterMyInstanceInAccessibleFromCodePlaceAction/> < /i:EventTrigger> < /i:Interaction.EventTriggers> < /StackPanel> < /ItemsPanelTemplate> < /ComboBox.ItemsPanel>
&LT; /组合框&GT;
答案 3 :(得分:0)
我在尝试从代码中设置ItemsPanel时找到了您的帖子,以便我可以实现VirtualizingStackPanel。当我的列表中有数百个项目时,性能很糟糕。无论如何..这就是我做到的。
1)自定义控制
2)自定义行为
- 您也可以将此行为应用于普通的ComboBox - 在每个实例或通过样式。
- 您可能还会公开超时值,以便可以在xaml中覆盖..
- 此外,当下拉列表本身打开时,它似乎不起作用。不确定为什么......从未调查过它
1 ..
public class KeyPressSelectionComboBox : ComboBox
{
private BindingExpression _bindingExpression;
public KeyPressSelectionComboBox()
: base()
{
Interaction.GetBehaviors(this).Add(new KeyPressSelectionBehavior());
Height = 22;
this.SelectionChanged += new SelectionChangedEventHandler(KeyPressSelectionComboBox_SelectionChanged);
}
void KeyPressSelectionComboBox_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
if (_bindingExpression == null)
{
_bindingExpression = this.GetBindingExpression(ComboBox.SelectedValueProperty);
}
else
{
if (this.GetBindingExpression(ComboBox.SelectedValueProperty) == null)
{
this.SetBinding(ComboBox.SelectedValueProperty, _bindingExpression.ParentBinding);
}
}
}
}
2 ...
/// <summary>
/// This behavior can be attached to a ListBox or ComboBox to
/// add keyboard selection
/// </summary>
public class KeyPressSelectionBehavior : Behavior<Selector>
{
private string keyDownHistory = string.Empty;
private double _keyDownTimeout = 2500;
private DateTime _lastKeyDownTime;
private DateTime LastKeyDownTime
{
get
{
if (this._lastKeyDownTime == null)
this._lastKeyDownTime = DateTime.Now;
return this._lastKeyDownTime;
}
set { _lastKeyDownTime = value; }
}
/// <summary>
/// Gets or sets the Path used to select the text
/// </summary>
public string SelectionMemberPath { get; set; }
/// <summary>
/// Gets or sets the Timeout (ms) used to reset the KeyDownHistory item search string
/// </summary>
public double KeyDownTimeout
{
get { return _keyDownTimeout; }
set { _keyDownTimeout = value; }
}
public KeyPressSelectionBehavior() { }
/// <summary>
/// Attaches to the specified object: subscribe on KeyDown event
/// </summary>
protected override void OnAttached()
{
base.OnAttached();
this.AssociatedObject.KeyDown += DoKeyDown;
}
void DoKeyDown(object sender, KeyEventArgs e)
{
// Create a list of strings and indexes
int index = 0;
IEnumerable<Item> list = null;
var path = SelectionMemberPath ??
this.AssociatedObject.DisplayMemberPath;
var evaluator = new BindingEvaluator();
if (path != null)
{
list = this.AssociatedObject.Items.OfType<object>()
.Select(item =>
{
// retrieve the value using the supplied Path
var binding = new Binding();
binding.Path = new PropertyPath(path);
binding.Source = item;
BindingOperations.SetBinding(evaluator,
BindingEvaluator.TargetProperty, binding);
var value = evaluator.Target;
return new Item
{
Index = index++,
Text = Convert.ToString(value)
};
});
}
else
{
list = this.AssociatedObject.Items.OfType<ListBoxItem>()
.Select(item => new Item
{
Index = index++,
Text = Convert.ToString(item.Content)
});
}
// Sort the list starting at next selectedIndex to the end and
// then from the beginning
list = list.OrderBy(item => item.Index <=
this.AssociatedObject.SelectedIndex ?
item.Index + this.AssociatedObject.Items.Count : item.Index);
// calculate how long has passed since the user typed a letter
var elapsedTime = DateTime.Now - this.LastKeyDownTime;
if (elapsedTime.TotalMilliseconds <= this.KeyDownTimeout)
{ /* if it's less than the timeout, add to the search string */
this.keyDownHistory += GetKeyValue(e);
}
else
{ /* otherwise replace it */
this.keyDownHistory = GetKeyValue(e);
}
// Find first starting with the search string
var searchText = this.keyDownHistory;
var first = list.FirstOrDefault(item =>
item.Text.StartsWith(searchText, StringComparison.InvariantCultureIgnoreCase));
if (first != null)
{ /* found */
this.AssociatedObject.SelectedIndex = first.Index;
}
else
{ /* not found - so reset the KeyDownHistory */
this.keyDownHistory = string.Empty;
}
// reset the last time a key was pressed
this.LastKeyDownTime = DateTime.Now;
}
/// <summary>
/// Gets the value of the pressed key,
/// specifically converting number keys from their "Dx" value to their expected "x" value
/// </summary>
/// <param name="e"></param>
/// <returns></returns>
private static string GetKeyValue(KeyEventArgs e)
{
string rValue = string.Empty;
switch (e.Key)
{
default:
rValue = e.Key.ToString();
break;
case Key.D0:
case Key.NumPad0:
rValue = (0).ToString();
break;
case Key.D1:
case Key.NumPad1:
rValue = (1).ToString();
break;
case Key.D2:
case Key.NumPad2:
rValue = (2).ToString();
break;
case Key.D3:
case Key.NumPad3:
rValue = (3).ToString();
break;
case Key.D4:
case Key.NumPad4:
rValue = (4).ToString();
break;
case Key.D5:
case Key.NumPad5:
rValue = (5).ToString();
break;
case Key.D6:
case Key.NumPad6:
rValue = (6).ToString();
break;
case Key.D7:
case Key.NumPad7:
rValue = (7).ToString();
break;
case Key.D8:
case Key.NumPad8:
rValue = (8).ToString();
break;
case Key.D9:
case Key.NumPad9:
rValue = (9).ToString();
break;
}
return rValue;
}
/// <summary>
/// Helper class
/// </summary>
private class Item
{
public int Index;
public string Text;
}
/// <summary>
/// Helper class used for property path value retrieval
/// </summary>
private class BindingEvaluator : FrameworkElement
{
public static readonly DependencyProperty TargetProperty =
DependencyProperty.Register(
"Target",
typeof(object),
typeof(BindingEvaluator), null);
public object Target
{
get { return GetValue(TargetProperty); }
set { SetValue(TargetProperty, value); }
}
}
}