Caliburn.Micro:网格未绑定/链接到x:名称

时间:2014-07-16 12:18:52

标签: c# xaml windows-phone-8 caliburn.micro

所以我正在使用Caliburn.Micro框架开发Windows Phone 8应用程序。我正在尝试创建一个网格,我在运行时在运行时添加/删除TextBlock等元素。我已经尝试了一些方法将我的代码绑定到x:Name但到目前为止还没有任何工作。

所以我尝试过的一件事就是在我的xaml中有一个占位符网格又名视图:

    <Grid x:Name="ContentPanel" Margin="0,97,0,0" Grid.RowSpan="2">
    </Grid>

然后我的ViewModel使用以下内容来绑定我的ContentPanel网格:

    private Grid contentPanel;
    public Grid ContentPanel
    {
        get
        {
            return contentPanel;
        }
        set
        {
            contentPanel = value;
            NotifyOfPropertyChange(() => ContentPanel);
        }
    }

然后我创建了一个TextBlock来添加到网格中:

TextBlock txt1 = new TextBlock();
txt1.Text = "2005 Products Shipped";
txt1.FontSize = 20;
txt1.FontWeight = FontWeights.Bold;
Grid.SetRow(txt1, 1);

最后我将TextBlock添加到我的网格中:

ContentPanel.Children.Add(txt1);

当我运行此代码ContentPanel时,结果是等于null,为什么会这样?不应该Caliburn使用属性x:Name="ContentPanel"自动绑定ContentPanel ContentPanel吗?

我很感激你在这件事上的帮助。

我需要解决的核心问题是: 我在我的应用程序中有一个登录页面,其中显示从服务器加载的一些图片和文本。如下所示,这是通过Image和TextBlock完成的。当该服务器处于脱机状态或wi-fi未启用时,我想用静态图像替换此图片+文本。 Aka我想从StackPanel中删除TextBlock。

我加载并显示我的服务器中的东西的部分效果很好,在我的xaml中看起来像这样:

<StackPanel Orientation="Horizontal" Background="White" DataContext="{Binding FeedItemsAnnounce,Mode=TwoWay}" >
<Image delay:LowProfileImageLoader.UriSource="{Binding ImagePath,Mode=TwoWay}" Margin="5" Width="170" Height="138">
    <i:Interaction.Triggers>
        <i:EventTrigger
        EventName="Tap">
            <cm:ActionMessage
        MethodName="LoadAnnouncement">
                <cm:Parameter Value="{Binding Link}"></cm:Parameter>
            </cm:ActionMessage>
        </i:EventTrigger>
    </i:Interaction.Triggers>
</Image>
<TextBlock Text="{Binding Title}" TextWrapping="Wrap" Width="160" Foreground="Black" FontSize="24" VerticalAlignment="Center" Margin="25,0,0,0"></TextBlock>
<i:Interaction.Triggers>
    <i:EventTrigger
        EventName="Tap">
        <cm:ActionMessage
        MethodName="LoadAnnouncement">
            <cm:Parameter Value="{Binding Link}"></cm:Parameter>
        </cm:ActionMessage>
    </i:EventTrigger>
</i:Interaction.Triggers>

因此,当服务器处于脱机状态/禁用wifi时,我想用它替换它。这样TextBlock就不再存在了:

<Image  delay:LowProfileImageLoader.UriSource="{Binding ImagePath,Mode=TwoWay}" DataContext="{Binding FeedItemsAdvertisement,Mode=TwoWay}" Margin="0,20,0,39" Width="380" Height="128">
<i:Interaction.Triggers>
    <i:EventTrigger
        EventName="Tap">
        <cm:ActionMessage
        MethodName="LoadAdvertisement" >
            <cm:Parameter Value="{Binding Link}"></cm:Parameter>
        </cm:ActionMessage>
    </i:EventTrigger>
</i:Interaction.Triggers>

这甚至可能吗?如果不是最好的半解决方案是什么?

编辑1:我已设法按照接受的答案中的说明设置流程。但我的BooleanToVisibilityConverter未被调用,但我的NotifyOfPropertyChange(() => IsConnectionAvailable);被调用。

我的财产:

private bool _isConnectionAvailable;

public bool IsConnectionAvailable
{
    get { return _isConnectionAvailable; }
    set
    {
        if (_isConnectionAvailable != value)
        {
            _isConnectionAvailable = value;
            NotifyOfPropertyChange(() => IsConnectionAvailable);
        }
    }
}

我如何更改bool:在我的构造函数中为我的ViewModel调用此代码(只是作为测试它是否正常工作):

IsConnectionAvailable = false;

TextBlock(没有触发器代码导致它与之前相同):

<TextBlock Text="{Binding Title}" Visibility="{Binding IsConnectionAvailable, Converter={StaticResource BoolToVisibility}}" TextWrapping="Wrap" Width="160" Foreground="Black" FontSize="24" VerticalAlignment="Center" Margin="25,0,0,0"></TextBlock>

就像Binding IsConnectionAvailable没有效果一样,因为我可以将我的Xaml中的名称IsConnectionAvailable更改为任何内容,我的NotifyOfPropertyChange(() => IsConnectionAvailable);仍会被调用。< / p>

有什么想法吗?

我甚至无法对Visibility="{Binding Path=IsVisibil,Mode=TwoWay}属性进行正常绑定public Visibility IsVisibil。我已经在其他课程中完成了这项工作,但即使这样做也不行?

编辑2:绑定不起作用的问题似乎在这段代码的某处:

<StackPanel Orientation="Horizontal" Background="White" DataContext="{Binding FeedItemsAnnounce,Mode=TwoWay}" >
                <Image delay:LowProfileImageLoader.UriSource="{Binding ImagePath,Mode=TwoWay}" Margin="5" Width="170" Height="138">
                    <i:Interaction.Triggers>
                        <i:EventTrigger
                        EventName="Tap">
                            <cm:ActionMessage
                        MethodName="LoadAnnouncement">
                                <cm:Parameter Value="{Binding Link}"></cm:Parameter>
                            </cm:ActionMessage>
                        </i:EventTrigger>
                    </i:Interaction.Triggers>
                </Image>
                <TextBlock Text="{Binding Title}" Visibility="{Binding Path=IsVisibil,Mode=TwoWay}" TextWrapping="Wrap" Width="160" Foreground="Black" FontSize="24" VerticalAlignment="Center" Margin="25,0,0,0"></TextBlock>
                <i:Interaction.Triggers>
                    <i:EventTrigger
                        EventName="Tap">
                        <cm:ActionMessage
                        MethodName="LoadAnnouncement">
                            <cm:Parameter Value="{Binding Link}"></cm:Parameter>
                        </cm:ActionMessage>
                    </i:EventTrigger>
                </i:Interaction.Triggers>
            </StackPanel>

EDIT 1和2的解决方案:我创建了一个x:Name&#34; Root&#34;在我的xaml结构的顶部。然后将绑定更改为:

ElementName=Root, Path=DataContext.IsVisibil

这是必需的,因为我尝试设置的可见性绑定是在另一个DataContxt内。

1 个答案:

答案 0 :(得分:5)

这不是使用CM的正确方法,在许多方面您会混淆CM中的模型和视图模型以及绑定功能。

您目前正在做什么

您正在尝试让CM框架在ViewModel上查找名为ContentPanel的属性,并自动找出Grid上的哪些属性将其绑定到...

由于以下几个原因,这不会起作用:

  1. 我不认为CM中存在Grid的约定 - 它不能以明显的方式绑定(它是一个布局容器)
  2. 网格不是支持数据的控件 - 它不知道如何使用集合并在框中显示动态行(它是布局容器)
  3. 你正在做什么并没有任何意义(你的UserControl中有一个网格实例,你还在ViewModel中实例化了一个网格 - 这是控件的两个独立实例 - 你可以&# 39; t&#39;将它们绑在一起 - 而不是它们如何运作)
  4. CM和Bindings

    使用元素名称绑定时,例如使用CM x:Name,它会尝试在ViewModel上找到与元素名称匹配的属性。此时,根据相关源控件的约定设置,CM将尝试自动连接所有的碎片。

    ConventionManager中包含默认约定,用于确定在使用元素名称绑定时要绑定的属性 - 例如对于TextBlockText上的TextBlock属性绑定到ViewModel上的目标属性。

    http://caliburnmicro.codeplex.com/SourceControl/latest#src/Caliburn.Micro.Platform/ConventionManager.cs - 查看ConventionManager上的类构造函数,以查看开箱即用的约定 - Grid不适用

    找到目标属性后,CM会将其绑定。

    (顺便说一句:值得注意的是,如果控件类型为ContentControl CM,则会执行一些组合魔术,因此您可以拥有包含其他视图模型的视图模型,并且具有所有视图模型在运行时绑定 - 非常适用于具有多个子窗口的屏幕等)

    你遇到的问题是Grid没有开箱即用的常规设置 - 这很可能是因为SL / WPF中的Grid主要用于布局,并不是真的一个数据容器&#39;或以任何方式识别数据(除了可以绑定的少数依赖属性) - 即我不认为可以绑定到网格并获得动态数量的列/行而无需进行一些定制对控制,因此省略任何惯例

    (考虑一下 - 如果你将一个网格绑定到一个集合,那么网格应该做什么......添加行或列?它不能以合理的方式得到支持)

    现在将它带回SL / WPF一秒钟:

    通常,如果你想要一个变量的项目列表,你需要绑定到一个继承自ItemsSource(或ItemsControl本身)的控件的ItemsControl属性。

    许多控件执行此操作:如果需要显示动态数量的项目,则通常会从ItemsControl继承。

    这与CM有什么关系?

    Caliburn Micro知道如何开箱即用ItemsControl。这意味着您可以在ViewModel上包含一个包含项集合的属性,并在绑定后在运行时获得这些属性的动态视图

    例如 - CM绑定ItemsControl可能如下所示:

    <ItemsControl x:Name="TextItems">
      <!-- host the items generated by this ItemsControl in a grid -->
      <ItemsControl.ItemsPanel>
        <ItemsPanelTemplate>
          <Grid/>
        </ItemsPanelTemplate>
      </ItemsControl.ItemsPanel>
      <!-- render each bound item using a TextBlock-->
      <ItemsControl.ItemTemplate>
        <DataTemplate>
          <TextBlock Text="{Binding SomeTextualProperty}"/>
        </DataTemplate>
      </ItemsControl.ItemTemplate>
    </ItemsControl>
    

    现在您只需要一个对象集合来绑定它 - 集合中的每个项目都成为控件中的一个新项目,其DataContext指向绑定项目。我已经假设你希望每个项目都是一个包含属性SomeTextualProperty的ViewModel - 我已经在这里定义了......

    // Provides a viewmodel for a textual item
    public class TextItemViewModel
    {
        public string SomeTextualProperty { get; set;}
    }
    

    应包含项目列表的VM需要具有要绑定的集合。

    (注意:由于您在运行时向其添加项目,因此您需要在集合更改时告知UI - ObservableCollection免费为您提供此项,因为它实现了集合更改的通知事件)

    // This is the viewmodel that contains the list of text items
    public class ScreenViewModel 
    {
        public ObservableCollection<TextItemViewModel> TextItems { get; set; }
    }
    

    我还会考虑不正确的方法

    您的ViewModel不应该知道您的View实现,即除非绝对必要,否则他们不应该引用任何类型的控件(我无法想到我必须将控件放入控件的时间VM)。 ViewModels应该对视图进行建模 - 但是他们不应该真正了解该视图包含的任何细节 - 这样它们更容易测试并且很容易被重用

    如果您遵循上述方法,您可以提供一个重用视图模型的应用程序,但为每个视图模型提供不同的视图。您可以通过在视图中将ItemsControl替换为其他类型的控件来尝试此操作(只要它具有数据感知功能,例如数据网格),并且VM仍可正常工作 - 虚拟机与视图无关。

    您在VM中使用Grid并不理想,因为Grid是可视控件,而不是数据。请注意,视觉效果是您的View,而ViewModel应该只包含通知视图发生事件的数据和事件

    如果我这样做 - 代码看起来更像我上面发布的代码。

    总结

    • 为要在ViewModel(TextItemViewModel
    • 中显示的信息建模
    • 使用更改感知集合(例如ScreenViewModel
    • )将这些对象的集合添加到主ViewModel(ObservableCollection
    • 使用标准添加/删除
    • 添加/删除集合中的项目
    • 使用ItemsControl绑定将视图中的x:Name绑定到ScreenViewModel上的集合
    • 在VM中添加/删除项目将触发属性更改通知。 ItemsControl会关注这些事件并相应地自行更新

    <强>附录

    您只需使用ObservableCollection<string>代替TextBlockViewModel就可以逃脱,但是如果您想要为绑定到网格的项目添加更多属性(例如作为标题的IsHeading属性,然后您可以在视图中使用粗体/斜体

    如果您只想使用strings,只需修改DataTemplate即可直接绑定到DataContext,而不是DataContext上的属性

    <ItemsControl x:Name="TextItems">
      <!-- host the items generated by this ItemsControl in a grid -->
      <ItemsControl.ItemsPanel>
        <ItemsPanelTemplate>
          <Grid/>
        </ItemsPanelTemplate>
      </ItemsControl.ItemsPanel>
      <!-- render each bound item using a TextBlock-->
      <ItemsControl.ItemTemplate>
        <DataTemplate>
          **<TextBlock Text="{Binding}"/> <!-- Bind direct -->**
        </DataTemplate>
      </ItemsControl.ItemTemplate>
    </ItemsControl>
    

    修改

    好的,在你的情况下它非常简单 - 你的ViewModel应该只是模拟服务器的状态:

    public class LoginPageViewModel
    {
        public bool IsConnectionAvailable { get; set; } // or whatever your variable should be called
    }
    

    然后使用转换器将文本块的可见性绑定到此:

    <TextBlock Visibility="{Binding IsConnectionAvailable, Converter={StaticResource BooleanToVisibilityConverter}}">
    

    您需要在某处声明转换器的静态资源(例如在控件本身或主资源字典中)

    看起来某个地方System.Windows.Controls已经定义了一个转换器,但是如果你找不到它,那么实现就非常简单了(你可以做得更好,以防止无效输入但为了简洁起见,我把它保持得很小):

    public class BooleanToVisibilityConverter : IValueConverter
    {
        public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
        {
            return (bool) value ? Visibility.Visible : Visibility.Collapsed;
        }
    
        public object ConvertBack(object value, Type targetType, object parameter,CultureInfo culture)
        {
            throw new NotImplementedException();
        }
    }
    

    您可能还希望在视图生命周期中更改状态从可用/不可用,因此在这种情况下,您可能希望使用内置于PropertyChangedBase的属性更改事件(Screen也继承)让视图知道属性何时发生变化

        private bool _isConnectionAvailable;
    
        public bool IsConnectionAvailable
        {
            get { return _isConnectionAvailable; }
            set
            {
                if (_isConnectionAvailable != value)
                {
                    _isConnectionAvailable = value;
                    NotifyOfPropertyChange(() => IsConnectionAvailable);
                }
            }
        }
    

    附录2

    我更喜欢简洁的CM语法,而不是在绑定动作消息时显式 - 所以你的XAML会改变:

    <Image delay:LowProfileImageLoader.UriSource="{Binding ImagePath,Mode=TwoWay}" DataContext="{Binding FeedItemsAdvertisement,Mode=TwoWay}" Margin="0,20,0,39" Width="380" Height="128">
    <i:Interaction.Triggers>
        <i:EventTrigger
            EventName="Tap">
            <cm:ActionMessage
            MethodName="LoadAdvertisement" >
                <cm:Parameter Value="{Binding Link}"></cm:Parameter>
            </cm:ActionMessage>
        </i:EventTrigger>
    </i:Interaction.Triggers>
    </Image>
    

    <Image delay:LowProfileImageLoader.UriSource="{Binding ImagePath,Mode=TwoWay}" DataContext="{Binding FeedItemsAdvertisement,Mode=TwoWay}" Margin="0,20,0,39" Width="380" Height="128" cal:Message.Attach="[Tap] = [LoadAdvertisement($dataContext.Link)]"></Image>
    

    (实际上这可能与$ dataContext.Link部分不对......但是它可能会再次......看到这里:http://caliburnmicro.codeplex.com/wikipage?title=All%20About%20Actions&referringTitle=Documentation