如何在面向Windows应用商店应用和WP7,WP8,WPF的便携式类库中处理图像?

时间:2012-09-20 12:05:05

标签: wpf microsoft-metro windows-runtime portable-class-library

我正在开发第一个针对以下目标的PCL:WSA(Windows应用商店应用),WPF,WP7,WP8。我们可以说它是一种rolerdex类型的应用程序,你有联系人,他们有联系方式和图像。 (它不是,但我无法提供有关应用程序的详细信息,因此我使用的是一个非常简单的示例)。以下是我的一些问题:)

  1. 我应该在PCL中有图像吗?
  2. 如果是:

    1. 如何在WSA中引用图像?
    2. 在不同项目中使用时,如何使用比例限定符等最佳地解决缩放问题?
    3. 我没有使用数据库而且图像不是从外部服务下载的 - 我想在本地,应用程序或PCL中保存图像(实际上并不多)。

      编辑: 我只是想显示图像。而已。这是一个静态的rolerdex,你不能添加新的人。我只是想显示5个人和他们的形象(在PCL中)。如果图像是Windows应用商店应用,如何引用图像?

      我有一个绑定,DataContext设置为PCL中的ViewModel。 ViewModel聚合要从模型显示的数据。我绑定的属性是MyImage。忽略其他平台,Uri会是什么样子?其他一切都很好。

      我真的只是希望得到这三个问题的帮助,尽管我非常感谢所有答案!

6 个答案:

答案 0 :(得分:23)

对于很多情况,图像是特定于平台的。它们需要满足设备本身的尺寸和DPI,并且需要适应应用程序的外观和感觉。对于这些情况,我希望View本身决定向用户显示哪些图像,可能是基于ViewModel提供的某种状态/模式。

但是,这些图像需要来自ViewModel,例如,在邮件应用程序中显示的发件人缩略图的情况下。在这些情况下,我让ViewModel返回某种与平台无关的图像概念(例如byte []),然后让特定于平台的项目将其转换为UI堆栈理解的内容(在XAML中,将是一个ImageSource)。

代码看起来像这样:

便携式项目

using System.IO;
using System.Reflection;

namespace Portable
{
    public class ViewModel
    {
        private byte[] _image = LoadFromResource("Image.png");

        public byte[] Image
        {
            get { return _image; }
        }

        private static byte[] LoadFromResource(string name)
        {
            using (Stream stream = typeof(ViewModel).GetTypeInfo().Assembly.GetManifestResourceStream("Portable." + name))
            {
                MemoryStream buffer = new MemoryStream();
                stream.CopyTo(buffer);

                return buffer.ToArray();
            }
        }
    }
}

注意:您需要删除或添加GetTypeInfo(),具体取决于您定位的平台。

我们在这里阅读嵌入式资源(属性 - >构建操作 - >嵌入式资源),但您可以想象这来自网络或其他地方。

Windows应用商店应用项目 在Windows应用商店应用中,您将有一个值转换器来转换为byte [] - >的ImageSource:

using System;
using System.IO;
using Windows.Storage.Streams;
using Windows.UI.Xaml.Data;
using Windows.UI.Xaml.Media.Imaging;

namespace App
{
    public class ByteToImageSourceValueConverter : IValueConverter
    {
        public object Convert(object value, Type targetType, object parameter, string language)
        {
            InMemoryRandomAccessStream s = new InMemoryRandomAccessStream();

            byte[] bytes = (byte[])value;
            Stream stream = s.AsStreamForWrite();
            stream.Write(bytes, 0, bytes.Length);
            stream.Flush();
            stream.Seek(0, SeekOrigin.Begin);


            BitmapImage source = new BitmapImage();
            source.SetSource(s);

            return source;           

        }

        public object ConvertBack(object value, Type targetType, object parameter, string language)
        {
            throw new NotImplementedException();
        }
    }
}

在View的后面代码中,设置DataContext:

DataContext = new ViewModel();

然后在View本身绑定到ViewModel.Image属性,并设置转换器:

<Page.Resources>
    <local:ByteToImageSourceValueConverter x:Name="ImageConverter"/>
</Page.Resources>

<Grid >
    <Image HorizontalAlignment="Left" Height="242" Margin="77,10,0,0" VerticalAlignment="Top" Width="278" Source="{Binding Image, Converter={StaticResource ImageConverter}}"/>

</Grid>

答案 1 :(得分:13)

当您需要在具有不同像素密度和不同屏幕尺寸的不同平台上缩放图像时,图像确实存在问题。

与此同时,图像处理库经常出现无法移植的问题 - 尤其是当他们在每个平台上使用硬件加速时。


假设你的rolodex应用程序以某种方式允许用户捕获图像,然后将它们上传到某个共享数据库/服务供以后查看,这就是我如何处理这个问题。这不是唯一的解决方案 - 这里没有“正确的方法”!

**捕捉和上传图片**

  1. 为了捕捉图片(来自文件夹或来自相机),我必须为每个平台使用本机钩子 - 所以不要用于此部分的PCL

  2. 如果我需要对图像进行一些设备上处理(例如调整大小或添加缩略图),那么这可能会在每个平台上以不同的方式完成 - 所以不要PCL。

  3. 一旦你有照片(例如在MemoryStream中编码为JPEG)并想将其上传到服务器,那么我可能会使用异步处理程序HttpWebRequest(这些是旧方法,没有任何内容)在常见的PCL代码中使用async / await)

  4. 服务器上正在运行什么......好吧,这几乎肯定不是PCL代码 - 我可能会使用那里的方法将图像调整为设备就绪尺寸 - 例如不同尺寸的缩略图

  5. **显示图片**

    1. 当我需要从数据库中显示某人的图像时,我可能会让服务器返回一个可用的缩略图URL列表 - 这是服务器代码所以不是PCL

    2. 实际要求提供联系人列表或联系人详细信息的应用程序代码 - 可能是PCL代码 - 并且可能会再次使用HttpWebRequest。

    3. 接受联系并在屏幕上呈现的视图代码?这可能是XAML - 我只是在每个平台上使用本机Image控件来使用和呈现适当的缩略图。

    4. **如果您在本地存储图像**

      1. 如果您在本地存储图像而不是使用中央服务器,那么您可能也需要使用非PCL代码。每个平台都有相同的基本类型的方法:LoadFile,SaveFile等 - 每个平台都提供创建,枚举,读取和写入文件夹和文件的机制,但每个平台都通过与文件系统不同的API(例如System)来实现。 WPF中的IO,WP7 Silverlight中的IsolatedStorage等)。

      2. 一旦你的文件进入了一个公共(ish)结构,那么PCL控制代码就能将每个文件视为一对字符串 - 它所在的文件夹和文件名。

      3. ...在您的UI层(例如XAML)中,您可能可以直接引用这些图像文件 - 可以使用特定的ValueConverter在每个平台上生成正确的文件名或文件流 - 例如在wp7中,您可以使用Windows Phone 7 Silverlight binding image from the IsolatedStorage

      4. 中的转换器

        所以,我的总结是:

        • 我会在任何可能的地方使用PCL代码
        • 但实际上PCL代码只会出现在控件和网络中 - 而不是图像采集,图像处理或图像渲染

        其他一些可能有用的事情:

        • 目前我发现将.Net4.5 async / await代码与“普通”代码混合起来非常困难 - 所以即使共享网络代码也可能很难 - 如果你为所有内容编写单独的代码,实际上可以节省时间:(
        • 为了帮助共享代码,我肯定会尝试使用某种形式的IoC或DI来尝试在需要的地方注入特定于平台的代码的特定实现(这是我在MvvmCross中做了很多 - 这就是@dsplaisted在http://blogs.msdn.com/b/dsplaisted/archive/2012/08/27/how-to-make-portable-class-libraries-work-for-you.aspx
        • 中讨论服务地点

答案 2 :(得分:2)

我倾向于同意图像处理可能应该是特定于平台的。作为一般规则,任何与视图相关的视图都不应包含在PCL中。上面的答案有一些很棒的技术细节,我不打算重复,但想发布链接到我放在一起讨论使用PCL的原理的幻灯片。 http://prezi.com/ipyzhxvxjvp-/sharing-is-caring-code-reuse-in-xaml-applications/

希望这能为总体设计策略提供一些一般性指导,并强化先前答案中的信息。

答案 3 :(得分:0)

我的第一直觉是说不,鉴于你所描述的,你不应该在你的PCL中有图像。

但如果我要这样做,可以通过不同库中的资源存储图像,并且可以为每个平台存储不同的图像,然后进行环境检查以确定要提取的图像。但是,如果你需要扩展,这种方法真的应该尝试。

按比例我的意思是你是开源这个库,1000个程序将使用它。如果这是为你自己内部使用3或4个应用程序,我会说不要打扰你自己的头痛。只需将图像传递到共享库中,或通过配置进行设置即可。

或者只是不通过PCL管理图像。因为图像处理在每个平台上都会发生变化并且不同,尤其是您提到的平台。你必然遇到一些奇怪的错误,它只是在其他平台上运行不同。

答案 4 :(得分:0)

我可能会将实际的图像文件放在每个应用程序中。我不认为你试图将它们嵌入PCL中会有很多价值。

在便携式ViewModels中,您可以通过Uri引用图像。不幸的是,Uri需要在每个平台上有所不同。对于Windows应用商店应用,它需要类似于ms-appx:////Assets/image.png,对于Windows Phone,我认为它只是/Assets/image.png

所以你需要一些代码才能找出正确的Uri格式。这可能是一个特定于平台的服务,具有ViewModels可以调用的可移植接口。您也可以创建一个可以进行所需转换的绑定转换器。

当然还有其他方法可以做到这一点 - 这就是我根据您提问中的信息选择的内容。

答案 5 :(得分:0)

您可以将视图模型上的图像作为“对象”类型,并使用接口(注入)来创建位图。

public interface IBitMapCreator
{
    object Create(string path);
}

public class MyViewModel
{
    private bool _triedToSetThumb;
    private readonly IBitMapCreator _bc;

    public MyViewModel(IBitMapCreator bc, string path)
    {
        _bc = bc;
        SetThumb(path);
    }

    public object Thumb { get; private set; }

    private void SetThumb(string path)
    {
        try
        {
            if (!_triedToSetThumb)
            {
                string ext = Path.GetExtension(path).ToUpper();

                if (
                    ext == ".JPG" ||
                    ext == ".JPEG" ||
                    ext == ".PNG" ||
                    ext == ".GIF" ||
                    ext == ".BMP" ||
                    false
                    )
                {
                    Thumb = _bc.Create(path);
                    OnPropertyChanged(new PropertyChangedEventArgs("Thumb"));
                }
            }
        }
        finally
        {
            _triedToSetThumb = true;
        }
    }

}

在WPF版本中,你可以这样做

class BitMapCreator : IBitMapCreator
{
    public object Create(string path)
    {
        return BitmapFrame.Create(new Uri(path));
    }
} 

通过使用此方法,您可以将数据绑定到图像,而不需要转换器。