Ich habe ein wirklich seltsames Problem, von dem ich fast überzeugt bin, dass es ein Bug ist.Bindung an UserControl in einer UserControl-Instanz Synchronisierung mit einer anderen Instanz
Ich habe drei UserControls, FolderView, LocalFolderView, RemoteFolderView. LocalFolderView und RemoteFolderView erben beide FolderView und werden in zwei anderen Steuerelementen LocalExplorer und RemoteExplorer verwandt.
LocalExplorer/RemoteExplorer haben eine Liste von Strings, die ich an die FolderView binde.
Das Problem ist, wenn ich mehr als 1 Instanz von LocalExplorer/RemoteExplorer, die ListBox in der FolderView für beide Explorer die gleichen Elemente anzeigen, aber die Abhängigkeitseigenschaften für die Steuerelemente sind scheinbar unterschiedlich.
Der Code ist wirklich lang, also werde ich versuchen, so viel wie möglich zu kondensieren. Momentan glaube ich, dass das Problem darin liegt, wie ich Dinge verbinde.
Hier ist die Kontrolle I mehr als eine Instanz dieses haben den Fehler aufweist:
LocalExplorer.xaml (RemoteExplorer.xaml folgt identisches Muster):
<UserControl x:Class="LocalExplorer"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:MyNamespace"
mc:Ignorable="d"
Width="Auto"
Height="Auto"
ClipToBounds="True"
x:Name="explorer">
<local:ExplorerBase Path="{Binding ElementName=explorer, Path=Path}" Orientation="{Binding ElementName=explorer, Path=Orientation}">
<local:ExplorerBase.FolderView>
<local:LocalFolderView x:Name="FolderView" Path="{Binding Path, RelativeSource={RelativeSource AncestorType={x:Type local:LocalExplorer}}}"/>
</local:ExplorerBase.FolderView>
</local:ExplorerBase>
</UserControl>
LocalExplorer.xaml.cs (RemoteExplorer. xaml.cs folgt identisches Muster):
public partial class Explorer : UserControl
{
#region Explorer
public Explorer()
{
InitializeComponent();
}
#endregion
#region Dependency Properties
public static DependencyProperty PathProperty = DependencyProperty.Register("Path", typeof(string), typeof(Explorer), new FrameworkPropertyMetadata("", FrameworkPropertyMetadataOptions.BindsTwoWayByDefault));
public string Path
{
get
{
return (string)GetValue(PathProperty);
}
set
{
SetValue(PathProperty, value);
}
}
#endregion
}
Weiter ExplorerBase ist, die UI-Logik spezifisch für alle Explorers beherbergt:
ExplorerBase.cs:
public partial class ExplorerBase : Control
{
public ExplorerBase()
{
this.DefaultStyleKey = typeof(ExplorerBase);
}
public override void OnApplyTemplate()
{
base.ApplyTemplate();
}
public static readonly DependencyProperty FolderViewProperty = DependencyProperty.Register("FolderView", typeof(object), typeof(ExplorerBase), null);
public object FolderView
{
get
{
return GetValue(FolderViewProperty);
}
set
{
SetValue(FolderViewProperty, value);
}
}
public static DependencyProperty PathProperty = DependencyProperty.Register("Path", typeof(string), typeof(ExplorerBase), new FrameworkPropertyMetadata("", FrameworkPropertyMetadataOptions.BindsTwoWayByDefault));
public string Path
{
get
{
return (string)GetValue(PathProperty);
}
set
{
SetValue(PathProperty, value);
}
}
}
I Schablone es Themen/generic.xaml Ansatz:
<Style TargetType="{x:Type Imagin.Controls:ExplorerBase}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="Imagin.Controls:ExplorerBase">
<ContentPresenter Content="{TemplateBinding FolderView}"/>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
Und schließlich die Folderview, die meiner Meinung nach ist das, was den Fehler hat. Die FolderView ist eine Basis für die tatsächlich verwendeten Steuerelemente LocalFolderView und RemoteFolderView. Beachten Sie, dass der Fehler unabhängig davon auftritt, ob ich LocalExplorer und RemoteExplorer oder eine von beiden verwende. Ich habe insgesamt nur zwei Instanzen gleichzeitig getestet.
FolderView.xaml:
<UserControl x:Class="FolderView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
Height="Auto"
Width="Auto"
x:Name="folderView">
<UserControl.Resources>
<BooleanToVisibilityConverter x:Key="BoolToVisibility" />
<Imagin.Data:InverseBooleanToVisibilityConverter x:Key="InverseBoolToVisibility" />
<Grid>
<ListBox x:Name="ListBox" AllowDrop="True" ItemsSource="{Binding Path=Items, RelativeSource={RelativeSource AncestorType={x:Type local:FolderView}}}" SelectionMode="Extended" ScrollViewer.HorizontalScrollBarVisibility="Disabled" ScrollViewer.VerticalScrollBarVisibility="Auto" IsSynchronizedWithCurrentItem="True">
<ListBox.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel IsItemsHost="True"/>
</ItemsPanelTemplate>
</ListBox.ItemsPanel>
<ListBox.Resources>
<DataTemplate DataType="{x:Type local:BindableFile}">
<local:Thumbnail FilePath="{Binding Path}" IsCheckBoxEnabled="False" ToolTip="{Binding ToolTip}" Title="{Binding Name}" Width="Auto" Height="Auto"/>
</DataTemplate>
<DataTemplate DataType="{x:Type local:BindableFolder}">
<local:Thumbnail FilePath="{Binding Path}" IsCheckBoxEnabled="False" ToolTip="{Binding ToolTip}" Title="{Binding Name}" Width="Auto" Height="Auto"/>
</DataTemplate>
</ListBox.Resources>
<ListBox.ItemContainerStyle>
<Style TargetType="{x:Type ListBoxItem}">
<Setter Property="IsSelected" Value="{Binding IsSelected, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />
</Style>
</ListBox.ItemContainerStyle>
</ListBox>
<DataGrid x:Name="DataGrid" ItemsSource="{Binding Items, RelativeSource={RelativeSource AncestorType={x:Type local:FolderView}}}" AutoGenerateColumns="False" BorderThickness="0" AlternationCount="2" GridLinesVisibility="None" HeadersVisibility="Column" CanUserAddRows="False" CanUserResizeColumns="True" IsSynchronizedWithCurrentItem="True" AllowDrop="True">
</DataGrid>
</Grid>
</UserControl>
FolderView.xaml.cs:
public abstract partial class FolderView : UserControl
{
#region DependencyProperties
public static DependencyProperty ItemsProperty = DependencyProperty.Register("Items", typeof(ObservableCollection<BindablePath>), typeof(FolderView), new FrameworkPropertyMetadata(new ObservableCollection<BindablePath>(), FrameworkPropertyMetadataOptions.BindsTwoWayByDefault));
public ObservableCollection<BindablePath> Items
{
get
{
return (ObservableCollection<BindablePath>)GetValue(ItemsProperty);
}
set
{
SetValue(ItemsProperty, value);
}
}
public static DependencyProperty PathProperty = DependencyProperty.Register("Path", typeof(string), typeof(FolderView), new FrameworkPropertyMetadata("", FrameworkPropertyMetadataOptions.BindsTwoWayByDefault, OnPathChanged));
public string Path
{
get
{
return (string)GetValue(PathProperty);
}
set
{
SetValue(PathProperty, value);
}
}
private static void OnPathChanged(DependencyObject Object, DependencyPropertyChangedEventArgs e)
{
FolderView FolderView = (FolderView)Object;
FolderView.Refresh();
FolderView.SearchTextBox.Text = string.Empty;
}
#endregion
#region Methods
public virtual void GetItems(string Path, out List<string> Folders, out List<string> Files)
{
Folders = default(List<string>);
Files = default(List<string>);
}
/// <summary>
/// Refreshes current path with contents.
/// </summary>
public virtual void Refresh()
{
//Used to debug property values at runtime. So far the values for each object instance are unique.
foreach (PropertyDescriptor descriptor in TypeDescriptor.GetProperties(this))
{
string name = descriptor.Name;
object value = descriptor.GetValue(this);
Console.WriteLine("{0}={1}", name, value);
}
}
/// <summary>
/// Populates controls with actual items via binding. We must do this on UI thread. This occurs immediately after <Refresh()>.
/// </summary>
/// <param name="Folders"></param>
/// <param name="Files"></param>
public virtual void Populate(List<FtpListItem> Folders, List<FtpListItem> Files)
{
}
public virtual void Populate(List<string> Folders, List<string> Files)
{
}
#endregion
#region FolderView
public FolderView()
{
InitializeComponent();
}
#endregion
}
LocalFolderView.cs (RemoteFolderView.cs folgt identisches Muster):
public sealed class LocalFolderView : FolderView
{
public override void GetItems(string Path, out List<string> Folders, out List<string> Files)
{
//These are my own functions
Folders = Directory.GetDirectories(Path);
Files = Directory.GetFiles(Path, null);
}
public override void Populate(List<string> Folders, List<string> Files)
{
int NumFolders = Folders.Count, NumFiles = Files.Count;
this.IsEmpty = NumFolders == 0 && NumFiles == 0 ? true : false;
if (Folders == null || Files == null || (NumFolders == 0 && NumFiles == 0)) return;
for (int j = 0, Count = NumFolders; j < Count; j++)
{
this.Items.Add(new BindableFolder(Folders[j]));
}
for (int j = 0, Count = NumFiles; j < Count; j++)
{
this.Items.Add(new BindableFile(Files[j]));
}
}
public override void Refresh()
{
base.Refresh();
this.Items.Clear();
//If directory doesn't exist, we don't want to enter it.
if (!System.IO.Directory.Exists(this.Path)) return;
List<string> Folders = null;
List<string> Files = null;
string CurrentPath = this.Path;
BackgroundWorker Worker = new BackgroundWorker();
Worker.DoWork += (s, e) =>
{
this.GetItems(CurrentPath, out Folders, out Files);
};
Worker.RunWorkerCompleted += (s, e) =>
{
//Start populating items
var DispatcherOperation = Application.Current.Dispatcher.BeginInvoke(new Action(() => this.Populate(Folders, Files)));
};
Worker.RunWorkerAsync();
}
}
Dinge zu beachten :
- Die DataGrids in den FolderViews in beiden Fällen werden ebenfalls mit identischen Elementen gefüllt.
- Die Pfadeigenschaft für jede FolderView-Instanz ist unterschiedlich.
- Dies tritt nur auf, nachdem die zweite Instanz mit Elementen gefüllt und dann versucht wird, die erste Instanz zu füllen. Wenn ich zuerst die erste Instanz bevölkere, passiert der zweiten überhaupt nichts.
- Wenn ich sage, dass beide Instanzen mit identischen Elementen gefüllt werden, meine ich, dass, wenn ich die erste auflege, die ersten Elemente in der zweiten erscheinen. Und wenn ich die Sekunde bevölkere, erscheinen die Elemente der Sekunde in der ersten.
- Und auch, wenn ich "populate" sage, ich meine nur, ich setze die
Path
Eigenschaft auf die FolderView.
Dinge, die ich habe versucht:
- die Art und Weise ändern I binden. Anstatt beispielsweise wie
Binding ElementName=explorer, Path=Property
zu binden, würde ich es zuBinding Property, RelativeSource={RelativeSource AncestorType={x:Type local:UserControlType}}
ändern. - Entfernen
x:Name
Eigenschaften aus verschiedenen Elementen. - Dumping FolderView-Eigenschaften/Werte. Ein Beispiel dafür oben in Quelle.
Ehrlich gesagt, weiß ich nicht, wie sonst zu debuggen. Ist das ein Fehler oder ist meine Bindungslogik nicht so logisch?
bearbeiten
Hier ist, wie ich zwei Explorer-Instanzen angezeigt:
<local:LocalExplorer />
<local:RemoteExplorer/>
Da sie beide ihre eigenen Instanzen, ich sehe nicht, wie entweder versehentlich auf einen anderen binden könnte, besonders wenn man bedenkt, wie tief verschachtelt sind die ListBoxen in der visuellen Struktur.
Omg, DANKE. –