Я работаю над настраиваемым элементом управления панелью, и одна из вещей, которые я пытаюсь сделать, это обмен файлами во время выполнения. Для этого управления два состояния: Максимизировано и Нормальное. Когда пользователь нажимает кнопку на элементе управления, состояние переключается. В этом элементе управления есть два свойства: MaximizedContent и MinimizedContent. Когда нажата кнопка для переключения состояний, свойство Content элемента управления необходимо поменять между MaximizedContent и MinimizedContent. Проблема возникает, когда привязки внутри MaximizedContent или MinimizedContent. Не похоже, что это часть "Дерева", и поэтому привязка не работает... по крайней мере, моя теория. Итак, мой вопрос: как сделать их частью дерева?
Здесь приведен упрощенный пример:
MainWindow.xaml
<Window x:Class="SwappingContentTest.MainWindow"
Loaded="Window_Loaded"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:local="clr-namespace:SwappingContentTest"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<StackPanel HorizontalAlignment="Left">
<Button x:Name="swapContentButton"
Click="swapContentButton_Click"
Content="Swap Content" />
<local:SwappableContentControl x:Name="swappableControl">
<local:SwappableContentControl.MaximizedContent>
<StackPanel>
<CheckBox x:Name="maximizedCheckBox"
Content="Maximized CheckBox" />
<Button x:Name="maximizedButton"
Content="Maximized Button"
IsEnabled="{Binding ElementName=maximizedCheckBox, Path=IsChecked}" />
</StackPanel>
</local:SwappableContentControl.MaximizedContent>
<local:SwappableContentControl.MinimizedContent>
<StackPanel>
<CheckBox x:Name="minimizedCheckBox"
Content="Minimized CheckBox" />
<Button x:Name="minimizedButton"
Content="Minimized Button"
IsEnabled="{Binding ElementName=minimizedCheckBox, Path=IsChecked}" />
</StackPanel>
</local:SwappableContentControl.MinimizedContent>
</local:SwappableContentControl>
<CheckBox x:Name="standardCheckBox"
Content="Standard CheckBox"
Margin="0,20,0,0"/>
<Button x:Name="standardButton"
Content="StandardButton"
IsEnabled="{Binding ElementName=standardCheckBox, Path=IsChecked}" />
</StackPanel>
</Window>
MainWindow.cs
namespace SwappingContentTest
{
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
private void Window_Loaded(object sender, RoutedEventArgs e)
{
swappableControl.SwapContent();
}
private void swapContentButton_Click(object sender, RoutedEventArgs e)
{
swappableControl.SwapContent();
}
}
}
SwappableContentControl.cs
namespace SwappingContentTest
{
public class SwappableContentControl : ContentControl
{
public static readonly DependencyProperty MaximizedContentProperty = DependencyProperty.Register("MaximizedContent", typeof(object), typeof(SwappableContentControl));
public static readonly DependencyProperty MinimizedContentProperty = DependencyProperty.Register("MinimizedContent", typeof(object), typeof(SwappableContentControl));
public static readonly DependencyProperty StateProperty = DependencyProperty.Register("State", typeof(SwappableContentControlState), typeof(SwappableContentControl),
new PropertyMetadata(new PropertyChangedCallback(StatePropertyCallback)));
public static void StatePropertyCallback(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
SwappableContentControl control = (SwappableContentControl)d;
if ((SwappableContentControlState)e.NewValue == SwappableContentControlState.Maximized)
{
control.Content = control.MaximizedContent;
}
else
{
control.Content = control.MinimizedContent;
}
}
public object MaximizedContent
{
get { return GetValue(MaximizedContentProperty); }
set { SetValue(MaximizedContentProperty, value); }
}
public object MinimizedContent
{
get { return GetValue(MinimizedContentProperty); }
set { SetValue(MinimizedContentProperty, value); }
}
public SwappableContentControlState State
{
get { return (SwappableContentControlState)GetValue(StateProperty); }
set { SetValue(StateProperty, value); }
}
public void SwapContent()
{
if (State == SwappableContentControlState.Maximized)
{
State = SwappableContentControlState.Normal;
}
else
{
State = SwappableContentControlState.Maximized;
}
}
}
}
Здесь ссылка на проект: http://www.freewebs.com/thrash505/SwappingContentTest.zip
Я предлагаю не обмениваться содержимым, а размещать два экземпляра ContentControl в вашем элементе управления и изменять видимость. В дополнение к чистоте в целом, это будет иметь преимущество в производительности только обновления макета управления и не заставляя деревья перестраиваться. Также это означает, что и ContentControls всегда остаются в логическом дереве, что упрощает их сопоставление в реализации вашего управления и правильное обновление привязок. Кроме того, вы получаете то преимущество, что их можно планировать отдельно, открывая дверь для приятных визуальных изменений состояния.
В исходном примере после замены содержимого будет создана следующая ошибка привязки:
Ошибка System.Windows.Data: 4: не удается найти источник для привязки с ссылка 'ElementName = maximizedCheckBox'. BindingExpression: Path = IsChecked; DataItem = NULL; целевой элемент 'Button' (Name= 'maximizedButton'); target свойство IsEnabled (тип 'Boolean')
FrameworkElement предоставляет два метода: AddLogicalChild и RemoveLogicalChild
Любые свойства зависимостей "Содержимое" должны предоставлять значение измененного обратного вызова. В этом обработчике обратного вызова сделайте вызов RemoveLogicalChild для старого контента и вызовите AddLogicalChild для нового содержимого. Это сохраняет логическое дерево в синхронизации и поддерживает модель DataContext.
Глядя на исходный пример, новая реализация MinimizedContent будет выглядеть примерно так:
// MinimizedContent
public static readonly DependencyProperty MinimizedContentProperty
= DependencyProperty.Register("MinimizedContent", typeof(object), typeof(SwappableContentControl),
new PropertyMetadata(new PropertyChangedCallback(OnMinimizedContentChanged)));
public object MinimizedContent
{
get { return GetValue(MinimizedContentProperty); }
set { SetValue(MinimizedContentProperty, value); }
}
private static void OnMinimizedContentChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
SwappableContentControl swappableContentControl = d as SwappableContentControl;
if (swappableContentControl == null)
return;
swappableContentControl.RemoveLogicalChild(e.OldValue);
swappableContentControl.AddLogicalChild(e.NewValue);
}
Выполнение вызовов AddLogicalChild и RemoveLogicalChild устраняет проблему, и привязки работают должным образом.