【问题标题】:Try to load images (categorized by city) from local DB to flowLayoutPanel尝试将图像(按城市分类)从本地数据库加载到 flowLayoutPanel
【发布时间】:2021-11-21 21:28:16
【问题描述】:

我正在尝试构建一个可以将图像插入本地数据库并将它们加载到 flowLayoutPanel 的表单。这是我的表单布局,我在“管理”选项卡中插入图像(图 1)。

在“浏览照片”选项卡中,我尝试通过单击按钮(图 2)将特定城市的照片加载到 flowLayoutPanel。

Insert images

Load images according to button pressed

My local database (data type)

我顺利插入照片,但尝试加载时出现问题。

这是我的代码:

    public Frm_MyAlbum()
    {
        InitializeComponent();
    }

    private void loadImage (int id, FlowLayoutPanel flp)
    {
        try
        {
            using (SqlConnection conn = new SqlConnection())
            {
                conn.ConnectionString = Settings.Default.Database1ConnectionString;

                SqlCommand command = new SqlCommand();
                command.CommandText = $"Select * from Photos where ID = {id}";
                command.Connection = conn;

                conn.Open();
                SqlDataReader DR = command.ExecuteReader();

                this.flowLayoutPanel1.Controls.Clear();

                while (DR.Read())
                {
                    byte[] bytes = (byte[])DR["Image"];
                    MemoryStream MS = new MemoryStream(bytes);

                    PictureBox pics;
                    pics = new PictureBox();
                    pics.Image = Image.FromStream(MS);
                    pics.Size = new Size(200, 160);
                    pics.SizeMode = PictureBoxSizeMode.StretchImage;

                    this.flowLayoutPanel1.Controls.Add(pics);
                }
            }
        }
        catch (Exception ex)
        {
            MessageBox.Show(ex.Message);
        }
    }


    private void GetID(string City, FlowLayoutPanel flp)
    {
        try
        {
            using (SqlConnection conn = new SqlConnection())
            {
                conn.ConnectionString = Settings.Default.Database1ConnectionString;
                conn.Open();

                SqlCommand command = new SqlCommand();
                command.CommandText = $"Select * from Photos where City = {City}";
                command.Connection = conn;

                
                SqlDataReader DR = command.ExecuteReader();        

                while (DR.Read())
                {
                    loadImage((int)DR["PhotoID"], flp);
                }
            }
        }
        catch (Exception ex)
        {
            MessageBox.Show(ex.Message);
        }
    }       

    private void button1_Click(object sender, EventArgs e) //button London
    {
        this.flowLayoutPanel1.Controls.Clear();
        GetID("London", flowLayoutPanel1);
    }

    private void button7_Click(object sender, EventArgs e) //button Add to DB
    {
        try
        {
            using (SqlConnection conn = new SqlConnection())
            {
                conn.ConnectionString = Settings.Default.Database1ConnectionString;

                SqlCommand command = new SqlCommand();
                command.CommandText = $"Insert into Photos(City, Description, Image) values(@City, @Desc, @Image)";
                command.Connection = conn;

                byte[] bytes;

                MemoryStream MS = new MemoryStream();
                this.pictureBox1.Image.Save(MS, System.Drawing.Imaging.ImageFormat.Jpeg);
                bytes = MS.GetBuffer();

                command.Parameters.Add("@City", SqlDbType.Text).Value = this.textBox2.Text;
                command.Parameters.Add("@Desc", SqlDbType.Text).Value = this.textBox1.Text;
                command.Parameters.Add("@Image", SqlDbType.Image).Value = bytes;

                conn.Open();
                command.ExecuteNonQuery();
                MessageBox.Show("Adding Successfully");
            }
        }
        catch (Exception ex)
        {
            MessageBox.Show(ex.Message);
        }
    }
    private void button8_Click(object sender, EventArgs e) //button browse
    {
        if (this.openFileDialog1.ShowDialog() == DialogResult.OK)
        {
            this.pictureBox1.Image = Image.FromFile(openFileDialog1.FileName);
        }
    }

当我运行代码时,我按下了 London(按钮 1)并且异常显示为“无效的列名 'London'”。 VS 表示 'command.CommandText = $"Select * from Photos where City = {City}";' 这一行有问题。

the bug

尝试了很多方法来重写它,但没有弄明白。 我应该怎么做才能解决这个问题?

提前谢谢你们!!

【问题讨论】:

  • 首先您将直接字符串传递到您的 sql 中(始终使用参数)。无论如何尝试 City = '{City}'
  • OK 稍后会尝试参数!我确实尝试使用'{City}',但弹出另一个异常“数据类型 text 和 varchar 在等于运算符中不兼容”
  • Image db 格式应该替换成varbinary(max) -- 这个:bytes = MS.GetBuffer(); 非常错误,你需要[MemoryStream].ToArray(),而不是GetBuffer() -- 强制重新格式化图像从任何东西到JPEG都是不好的。 -- 最后,这个:this.flowLayoutPanel1.Controls.Clear(); 可能是您在 WinForms 应用程序中可以做的最糟糕的事情之一。但是你还有其他突出的问题。
  • 谢谢 Jimi,我是编程新手,非常感谢您的意见。查一下你提到的几点。

标签: c# database winforms sqlcommand flowlayoutpanel


【解决方案1】:

**简短回答:** 问题出在查询的命令文本中。您还忘记添加包含城市名称的参数

从表单中分离数据 (MVVM)

在现代编程中,倾向于将数据(=模型)与数据的显示方式(=视图)分开。

分离的好处是可以在其他View中复用模型,可以改变模型而不需要改变视图。例如,如果您决定从文件而不是数据库中获取图像,您的表单将不必注意。您还可以更改视图而无需更改模型。例如,如果您打算不显示图像,则不必更改模型。最后,在没有表单的情况下对模型进行单元测试会更容易。您可以模拟模型中的数据,以便在没有真实数据的情况下向其他人展示您的视图。

您经常需要一个适配器类,通常称为 ViewModel 以将模型连接到视图。这三个类一起缩写为 MVVM。考虑阅读一些有关 MVVM 的背景信息。

你的模型

所以你需要一个代表你的照片的类。大概是这样的:

class Photo
{
    public int Id {get; set;}
    public string City {get; set;}
    public string Description {get; set;}
    public Image Image {get; set;}
}

显然,您可以将照片存储在某个地方,以便以后检索它们,即使在您重新启动计算机之后也是如此。这样的类通常被称为Repository(一个仓库,您可以在其中存储物品并稍后检索它们)

interface IPhotoRepository
{
    int AddP(Photo photo);         // returns the Id of the added Photo
    Photo Fetch(int photoId);      // returns Photo with Id or null
    ...
}

考虑添加更新和删除照片的功能。这超出了您的问题范围。

您还需要一个输入城市的字符串名称并返回在该城市拍摄的所有照片的方法:

    IEnumerable<Photo> FetchByCity(string cityName);

接口的实现:

class PhotoRepository : IPhotoRepository
{
    private string ConnectionString => ...

    public IEnumerable<Photo> FetchByCity(string cityName)
    {
        using (var dbConnection = new SqlConnection(this.ConnectionString))
        {
            using (var dbCommand = dbConnection.CreateCommand())
            {
                const string sqlText = "Select Id, Description, Image"
                + " from Photos where City = @City";

                dbCommand.CommandText = sqlText;
                dbCommand.Parameters.AddWithValue("@City", cityName);
                dbConnection.Open();
                
                // execute the command and return the fetched Photos:
                using (var dbReader = dbCommand.ExecuteReader())
                {
                    while (dbReader.Read())
                    {
                        // There is still a fetched row to process:
                        Photo fetchedPhoto = new Phto
                        {
                            Id = dbReader.GetInt64(0),
                            City = cityName,
                            Description = dbReader.GetString(1),
                            Image = (Image) dbReader.GetValue(2),
                            // I'm not not sure how to read an Image
                        };
                        yield return fetchedPhoto;
                    }
                }
            }
        }
    }

    // TODO: implement other methods
}

在您的表单中:

private IPhotoRepository PhotoRepository {get;} ...
// fill this in the constructor with a new PhotoRepository

private ICollection<Photo> FetchPhotosByCity(string cityName)
{
    return this.PhotoRepository.FetchByCity(cityName).ToList();
}

因为您将模式(= 类 Photo 和 PhotoRepository)与视图(= 表单)分开,所以您可以在没有表单的情况下对数据库访问进行单元测试。您还看到您的错误与您的表单无关。

如果你想用一些模拟数据显示你的表单,你只需创建一个实现 IPhotoRepository 的类并用一些模拟图像填充它,而不需要数据库。

很容易看出,如果您以后决定将照片保存在文件中,您的表单就不必更改。如果您向照片添加属性,则无需更改表单。

【讨论】:

  • 亲爱的 Harald,非常感谢您的详尽解释。我只是通过将数据类型从文本更改为 nvarchar(这是数据库无法告诉我的 Sql 命令的主要原因)并删除额外的 Control.Clear(); 来解决这个问题。但是,我相信您提到的设置参数非常重要,并且将是我需要努力使其变得更好的任务。我正在检查 MVVM 和您发布的示例代码。再次感谢您的帮助,真的很有帮助!
猜你喜欢
  • 2011-02-23
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-06-15
  • 1970-01-01
  • 2013-07-12
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多