我的智慧结束了,这让我非常沮丧。
在我的计划中有a)ListBox
显示位置列表,b)TabControl
显示从上述ListBox
中选择的位置的详细信息
两者都有一个上下文菜单,其中包含绑定到ViewModel上的方法的各种操作。但是,当点击ContextMenu
中的项目时,我会得到一个异常,告诉我,找不到任何方法。
我知道ContextMenu
不是可视化树的一部分,因此正常的数据绑定不起作用。因此,我必须使用PlacementTarget
属性。
这是我的问题:1。VS 2013设计师甚至不认识这个属性并不断告诉我他无法解决它。
它根本不起作用,我不明白为什么。
我使用Caliburn.Micro作为我的MVVM Framework和Blend样式代码来将XAML连接到我的VM属性。我尝试了一切,我要么得到一个例外,要么在另一个ViewModel上调用该方法(我用一个来显示一个位置的细节)
这是我的usercontrol:
<UserControl 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:cal="http://www.caliburnproject.org"
xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
xmlns:vm="clr-namespace:RpgTools.LocationPresenter.ViewModels"
x:Class="RpgTools.LocationPresenter.Views.LocationView"
mc:Ignorable="d"
d:DataContext="{d:DesignInstance IsDesignTimeCreatable=True, Type={x:Type vm:LocationViewModel}}"
cal:Bind.AtDesignTime="True"
Padding="5">
<Grid>
<!-- Code left out for brevity -->
<ScrollViewer Margin="5,3,3,5"
DockPanel.Dock="Top">
<ListBox x:Name="Locations"
SelectedItem="SelectedLocation"
ItemsSource="{Binding Locations}"
Tag="{Binding RelativeSource={RelativeSource AncestorType={x:Type UserControl}}, Path=DataContext}">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<StackPanel.ContextMenu>
<ContextMenu DataContext="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type ContextMenu}}, Path=PlacementTarget.Tag}">
<MenuItem Header="Delete">
<i:Interaction.Triggers>
<i:EventTrigger EventName="Click">
<cal:ActionMessage MethodName="Close" />
</i:EventTrigger>
</i:Interaction.Triggers>
</MenuItem>
</ContextMenu>
</StackPanel.ContextMenu>
<TextBlock Text="{Binding Value.Name}" />
<TextBlock Text="{Binding Key, StringFormat=' (\{0\})'}" />
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
<i:Interaction.Triggers>
<i:EventTrigger EventName="MouseDoubleClick">
<cal:ActionMessage MethodName="OpenLocationTab">
<cal:Parameter Value="{Binding SelectedItem, ElementName=Locations}" />
</cal:ActionMessage>
</i:EventTrigger>
</i:Interaction.Triggers>
</ListBox>
</ScrollViewer
<TabControl x:Name="Items"
Grid.Column="2"
Grid.Row="0"
Margin="3,5,5,3"
Visibility="{Binding TabControlVisible, Converter={StaticResource BooleanToVisibilityConverter}}">
<TabControl.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal"
x:Name="TabHeaderStackPanel">
<StackPanel.ContextMenu>
<ContextMenu DataContext="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type UserControl}}, Path=DataContext}">
<MenuItem Header="Close This"
cal:Message.Attach="Close()" />
</ContextMenu>
</StackPanel.ContextMenu>
<TextBlock Text="{Binding DisplayName}" />
<Button Padding="10,0,0,0"
Content="X"
Style="{DynamicResource NoChromeButton}"
cal:Message.Attach="CloseTab($dataContext)" />
</StackPanel>
</DataTemplate>
</TabControl.ItemTemplate>
</TabControl>
</Grid>
</UserControl>
以上是我的ViewModel视图:
namespace RpgTools.LocationPresenter.ViewModels
{
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel.Composition;
using System.Linq;
using Caliburn.Micro;
using PropertyChanged;
using RpgTools.Core;
using RpgTools.Core.Common;
using RpgTools.Core.Contracts;
using RpgTools.Core.Models;
using RpgTools.Core.Models.Locations;
using RpgTools.Locations;
/// <summary>Represents the location view model.</summary>
[ImplementPropertyChanged]
[RpgModuleMetadata(Name = "Locations")]
[Export(typeof(IRpgModuleContract))]
public class LocationViewModel : Conductor<LocationDetailsViewModel>.Collection.OneActive, IRpgModuleContract
{
/// <summary>Infrastructure. Holds a reference to the location repository.</summary>
private readonly ILocationRepository locationRepository;
/// <summary>Infrastructure. Holds a reference to the window manager.</summary>
private readonly IWindowManager windowManager;
/// <summary>Infrastructure. Holds a reference to the event aggregator.</summary>
private readonly IEventAggregator eventAggregator;
/// <summary>Initialises a new instance of the <see cref="LocationViewModel"/> class.</summary>
public LocationViewModel()
{
if (Execute.InDesignMode)
{
this.LocationSelectorVisible = true;
this.SaveButtonVisible = true;
this.TabControlVisible = true;
this.CheckListItems = new ObservableCollection<CheckListItem>
{
new CheckListItem("All", true),
new CheckListItem("Location", false)
};
var guids = new[] { Guid.NewGuid(), Guid.NewGuid() };
this.Locations = new DictionaryRange<Guid, Location>
{
{
guids[0],
new City
{
Id = guids[0],
Name = "Stromsang",
Description = "A City"
}
},
{
guids[1],
new Location
{
Id = guids[1],
Name = "Forst",
Description = "A place with trees"
}
}
};
}
}
/// <summary>Initialises a new instance of the <see cref="LocationViewModel"/> class.</summary>
/// <param name="eventAggregator">The event aggregator.</param>
/// <param name="windowManager">The window manager.</param>
[ImportingConstructor]
public LocationViewModel(IEventAggregator eventAggregator, IWindowManager windowManager)
{
this.locationRepository = new LocationRepositoryFactory(new DatabaseSeviceClient()).ForDefaultCulture();
this.eventAggregator = eventAggregator;
this.windowManager = windowManager;
// Set the initial visibilities of controls.
this.LocationSelectorVisible = true;
this.SaveButtonVisible = false;
this.TabControlVisible = false;
// Generate the check list items
this.CheckListItems = new ObservableCollection<CheckListItem>();
this.CheckListItems.Insert(0, new CheckListItem(Constants.AllOptions, true));
}
/// <summary>Gets or sets a value indicating whether the location selector is visible.</summary>
public bool LocationSelectorVisible { get; set; }
/// <summary>Gets or sets a value indicating whether the save button is visible.</summary>
public bool SaveButtonVisible { get; set; }
/// <summary>Gets or sets a value indicating whether the tab control is visible.</summary>
public bool TabControlVisible { get; set; }
/// <summary>Gets or sets the locations.</summary>
public IDictionaryRange<Guid, Location> Locations { get; set; }
/// <summary>Gets or sets the list of check list items.</summary>
public ObservableCollection<CheckListItem> CheckListItems { get; set; }
/// <summary>Toggles the visibility of the location list.</summary>
public void ToggleLocationList()
{
this.LocationSelectorVisible = !this.LocationSelectorVisible;
}
/// <summary>Opens a location tab in the tab control.</summary>
/// <param name="location">The location.</param>
public void OpenLocationTab(KeyValuePair<Guid, Location> location)
{
if (this.Items.All(s => s.DisplayName != location.Value.Name) && location.Value != null)
{
// Make the tab control viosible to the user.
this.TabControlVisible = true;
this.ActivateItem(new LocationDetailsViewModel(this.eventAggregator, this.windowManager)
{
Location = location.Value
});
this.LocationSelectorVisible = false;
this.SaveButtonVisible = true;
}
}
/// <summary>Closes a tab from the location tab control.</summary>
/// <param name="detailsViewModel">The data context the tab belongs to.</param>
public void CloseTab(LocationDetailsViewModel detailsViewModel)
{
this.CloseItem(detailsViewModel);
if (!this.Items.Any())
{
this.TabControlVisible = false;
this.LocationSelectorVisible = true;
this.SaveButtonVisible = false;
}
}
/// <summary>Closes all location tabs.</summary>
/// <param name="locations">The locations to close.</param>
public void CloseTabs(ICollection<LocationDetailsViewModel> locations)
{
foreach (LocationDetailsViewModel viewModel in locations)
{
this.CloseTab(viewModel);
}
}
/// <summary>Closes a collection of tabs, except one.</summary>
/// <param name="locations">The locations to close.</param>
/// <param name="location">The location to except from closing.</param>
public void CloseTabsExcept(ICollection<LocationDetailsViewModel> locations, LocationDetailsViewModel location)
{
foreach (LocationDetailsViewModel viewModel in locations.Where(vm => vm.DisplayName != location.DisplayName))
{
this.CloseTab(viewModel);
}
}
/// <summary>Loads the location ids from the location repository.</summary>
public void LoadLocations()
{
this.Locations = this.locationRepository.FindAll();
}
/// <summary>Loads the locations types and updates the filter list.</summary>
public void LoadLocationTypes()
{
IEnumerable<string> types = this.Locations.Values.Select(l => l.GetType().Name);
types.Where(t => this.CheckListItems.All(i => i.Name != t)).ToList().ForEach(t => this.CheckListItems.Add(new CheckListItem(t, true)));
}
/// <summary>Filters the locations based on the checked check boxes.</summary>
public void FilterLocations()
{
// Check if the locations have been loaded.
// if not just return.
if (this.Locations == null)
{
return;
}
// If the all item is checked, we can safely return all locations without filtering.
if (this.CheckListItems.Any(i => (i.Name == Constants.AllOptions && (i.IsChecked != null && (bool)i.IsChecked))))
{
this.Locations = this.locationRepository.FindAll();
return;
}
// Get the checked items.
var checkedBoxes = this.CheckListItems.Where(i => (i.IsChecked != null && (bool)i.IsChecked));
this.Locations = new DictionaryRange<Guid, Location>(this.locationRepository.FindAll().Where(l => checkedBoxes.Any(b => b.Name == l.Value.GetType().Name)).ToDictionary(x => x.Key, x => x.Value));
}
/// <summary>Toggles check list item.</summary>
/// <param name="sender">The item that changed.</param>
/// <param name="isChecked">The state of the item.
/// </param>
public void FilterChecklist(string sender, bool isChecked)
{
// Check if the locations have been loaded.
// if not just return.
if (this.Locations == null)
{
return;
}
// Check if the sender was the "all" item.
if (sender == Constants.AllOptions)
{
// Set all items to be set if "all" was checked.
// Otherwise set all items to be unchecked.
if (isChecked)
{
foreach (CheckListItem item in this.CheckListItems)
{
item.IsChecked = true;
}
}
else
{
foreach (CheckListItem item in this.CheckListItems)
{
item.IsChecked = false;
}
}
}
else
{
// Since we didn't change the all item
// we have to check if all others are checked
// to determine the correct state of the all item.
var allItem = this.CheckListItems.Single(i => i.Name == Constants.AllOptions);
var itemsWithoutAll = new List<CheckListItem>(this.CheckListItems.Where(i => i != allItem));
if (itemsWithoutAll.All(i => i.IsChecked != null && (bool)i.IsChecked))
{
allItem.IsChecked = true;
}
else if (itemsWithoutAll.Any(i => i.IsChecked != null && (bool)i.IsChecked))
{
allItem.IsChecked = null;
}
else
{
allItem.IsChecked = false;
}
}
}
/// <summary>Saves a location to the repository.</summary>
/// <param name="viewModel">The view model with the location to be saved.</param>
public void SaveLocation(LocationDetailsViewModel viewModel)
{
// Get the location to save
Location location = viewModel.Location;
// Save the location to the repository
this.locationRepository.Write(location);
// Reload the screen.
this.CloseTab(viewModel);
this.OpenLocationTab(new KeyValuePair<Guid, Location>(location.Id, location));
}
/// <summary>Saves all currently opened locations to the repository.</summary>
/// <param name="locationDetails">The location detail view models.</param>
public void SaveAllLocations(ICollection<LocationDetailsViewModel> locationDetails)
{
foreach (LocationDetailsViewModel viewModel in locationDetails)
{
this.SaveLocation(viewModel);
}
}
/// <summary>Deletes a location from the repository.</summary>
/// <param name="viewModel">The location to be deleted.</param>
public void DeleteLocation(object viewModel)
{
// Get the location to save
Location location = ((LocationDetailsViewModel)viewModel).Location;
this.locationRepository.Delete(location);
this.Locations.Remove(location.Id);
}
public void CreateLocation()
{
throw new NotImplementedException();
}
public void Close()
{
throw new NotImplementedException();
}
}
}
我得到的例外情况与两个上下文菜单基本相同,这里是列表上下文菜单的详细信息:
System.Exception was unhandled
HResult=-2146233088
Message=No target found for method Close.
Source=Caliburn.Micro.Platform
StackTrace:
bei Caliburn.Micro.ActionMessage.Invoke(Object eventArgs)
bei System.Windows.Interactivity.TriggerBase.InvokeActions(Object parameter)
bei System.Windows.Interactivity.EventTriggerBase.OnEvent(EventArgs eventArgs)
bei System.Windows.Interactivity.EventTriggerBase.OnEventImpl(Object sender, EventArgs eventArgs)
bei System.Windows.RoutedEventHandlerInfo.InvokeHandler(Object target, RoutedEventArgs routedEventArgs)
bei System.Windows.EventRoute.InvokeHandlersImpl(Object source, RoutedEventArgs args, Boolean reRaised)
bei System.Windows.UIElement.RaiseEventImpl(DependencyObject sender, RoutedEventArgs args)
bei System.Windows.UIElement.RaiseEvent(RoutedEventArgs e)
bei System.Windows.Controls.MenuItem.InvokeClickAfterRender(Object arg)
bei System.Windows.Threading.ExceptionWrapper.InternalRealCall(Delegate callback, Object args, Int32 numArgs)
bei MS.Internal.Threading.ExceptionFilterHelper.TryCatchWhen(Object source, Delegate method, Object args, Int32 numArgs, Delegate catchHandler)
bei System.Windows.Threading.DispatcherOperation.InvokeImpl()
bei System.Windows.Threading.DispatcherOperation.InvokeInSecurityContext(Object state)
bei System.Threading.ExecutionContext.RunInternal(ExecutionContext executionContext, ContextCallback callback, Object state, Boolean preserveSyncCtx)
bei System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state, Boolean preserveSyncCtx)
bei System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state)
bei System.Windows.Threading.DispatcherOperation.Invoke()
bei System.Windows.Threading.Dispatcher.ProcessQueue()
bei System.Windows.Threading.Dispatcher.WndProcHook(IntPtr hwnd, Int32 msg, IntPtr wParam, IntPtr lParam, Boolean& handled)
bei MS.Win32.HwndWrapper.WndProc(IntPtr hwnd, Int32 msg, IntPtr wParam, IntPtr lParam, Boolean& handled)
bei MS.Win32.HwndSubclass.DispatcherCallbackOperation(Object o)
bei System.Windows.Threading.ExceptionWrapper.InternalRealCall(Delegate callback, Object args, Int32 numArgs)
bei MS.Internal.Threading.ExceptionFilterHelper.TryCatchWhen(Object source, Delegate method, Object args, Int32 numArgs, Delegate catchHandler)
bei System.Windows.Threading.Dispatcher.LegacyInvokeImpl(DispatcherPriority priority, TimeSpan timeout, Delegate method, Object args, Int32 numArgs)
bei MS.Win32.HwndSubclass.SubclassWndProc(IntPtr hwnd, Int32 msg, IntPtr wParam, IntPtr lParam)
bei MS.Win32.UnsafeNativeMethods.DispatchMessage(MSG& msg)
bei System.Windows.Threading.Dispatcher.PushFrameImpl(DispatcherFrame frame)
bei System.Windows.Threading.Dispatcher.PushFrame(DispatcherFrame frame)
bei System.Windows.Threading.Dispatcher.Run()
bei System.Windows.Application.RunDispatcher(Object ignore)
bei System.Windows.Application.RunInternal(Window window)
bei System.Windows.Application.Run(Window window)
bei System.Windows.Application.Run()
bei RpgTools.Main.App.Main() in e:\Users\Robert\Documents\Visual Studio 2013\Projects\RpgTools\Tools\obj\Debug\App.g.cs:Zeile 0.
bei System.AppDomain._nExecuteAssembly(RuntimeAssembly assembly, String[] args)
bei System.AppDomain.ExecuteAssembly(String assemblyFile, Evidence assemblySecurity, String[] args)
bei Microsoft.VisualStudio.HostingProcess.HostProc.RunUsersAssembly()
bei System.Threading.ThreadHelper.ThreadStart_Context(Object state)
bei System.Threading.ExecutionContext.RunInternal(ExecutionContext executionContext, ContextCallback callback, Object state, Boolean preserveSyncCtx)
bei System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state, Boolean preserveSyncCtx)
bei System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state)
bei System.Threading.ThreadHelper.ThreadStart()
InnerException: