明经CAD社区

 找回密码
 注册

QQ登录

只需一步,快速开始

搜索
查看: 542|回复: 1

[图形系统] 魔改系列-11剪贴板和OLE对象

[复制链接]
发表于 2024-9-3 23:29:52 | 显示全部楼层 |阅读模式
本帖最后由 你有种再说一遍 于 2024-9-4 19:49 编辑

魔改系列-11剪贴板和OLE对象
# Acad的剪贴板原理
它的原理就是开一个dwg数据库,把块存入这个dwg数据库并保存到临时文件夹,然后剪贴板写入TagClipboardInfo结构:临时dwg文件路径,插入点,包围盒边界都在这里.
https://gitee.com/inspirefunctio ... TagClipboardInfo.cs

## IFox剪贴板
我做了一件事情,全部储存为07版本,这样后面版本通用剪贴板.
由于记录了上次剪贴板到配置,并且拦截了粘贴命令,所以关闭cad再开,仍然能够粘贴上次的图元过来,可谓是方便极了.
https://gitee.com/inspirefunctio ... tShared/Copyclip.cs
IFox剪贴板你会发现生成WMF有bug,高版本不能用,或者用不好,我也不知道怎么办,嘻嘻.

代码最后有一点点测试OLE内容,本来打算做一个Ole2FrameEx装饰类来映射Acad内部的Ole2Frame,并且提供DllImport的方法来着.
不过需要收集OLE函数,并且知道参数用途,需要做很多工作.

# ARX后台直接生成图像对象
下面文章的链接:
https://adndevblog.typepad.com/a ... sing-objectarx.html

ObjectARX中,我们使用两个类来表示图像.
AcDbRasterImage(图像实体)与AcDbRasterImageDef(图像定义).
它们关系很像块定义(块表记录)与块插入实体(块参照/块表记录引用)之间的关系.

图像实体是一个可绘制可选择的Acad实体,它在模型或图纸空间的特定位置和方向放置光栅图像.
图像实体与一个图像定义对象精确链接,它向该对象发送显示和绘图所需的图像处理操作请求.

图像定义管理所有图像信息,而图像实体相对较小.
图像实体有图像位置,方向,剪辑边界,图像淡入淡出,对比度,亮度参数,以及其他典型的AcDbEntity属性,如图层和颜色.

AcDbRasterImageDef类是在名为acISMui.arx的ObjectARX应用程序中实现的.
你的应用程序必须链接到ObjectARX API库acISMobj18.lib,才能使用这个类中的任何方法.
请注意,这个类上的AcDbObject方法可以在不链接到acISMobj18.lib的情况下访问,只需将对象指针(例如,由acdbOpenObject返回的指针)转换为AcDbObject类即可.

链接器->输入->附加依赖项 添加 acISMobjXX.lib

初始化中:
acrxLoadModule(_T("acISMui.arx"),false);
//否则
pDict->setAt(szName, pImageDef, objID);//返回 eNoClassId

```c++
// 函数:InsertImage
// 描述:在Acad数据库中插入一个图像.
void InsertImage() {
    // 定义图像的名称和文件路径.
    ACHAR* szName = _T("MyTest"); // 图像名称
    ACHAR *fileName = _T("C:\\temp\\newImage.jpeg"); // 图像文件路径

    // 定义图像的插入点.
    AcGePoint3d org(10,10,0);

    // 获取当前工作数据库.
    AcDbDatabase *pDb = acdbHostApplicationServices()->workingDatabase();

    // 创建一个新的图像定义对象.
    AcDbRasterImageDef* pImageDef = new AcDbRasterImageDef();
    // 设置图像文件名.
    Acad::ErrorStatus es = pImageDef->setSourceFileName(fileName);
    if(es != Acad::eOk) {
        delete pImageDef;
        return;
    }
    // 加载图像定义.
    es = pImageDef->load();
    ASSERT(es == Acad::eOk);

    // 获取或创建图像字典.
    AcDbObjectId dictID = AcDbRasterImageDef::imageDictionary(pDb);
    // 获取失败,结束
    if (dictID==AcDbObjectId::kNull) {
        es = AcDbRasterImageDef::createImageDictionary(pDb, dictID);
        if(es!= Acad::eOk) {
            delete pImageDef;
            ads_printf(_T("Could not create dictionary"));
            return;
        }
    }

    // 打开图像字典
    AcDbDictionary* pDict = NULL;
    es = acdbOpenObject((AcDbObject*&)pDict, dictID, AcDb::kForWrite);
    if(es != Acad::eOk) {
        delete pImageDef;
        ads_printf(_T("Could not open dictionary"));
        return;
    }

    // 检查图像是否已存在
    BOOL bExist = pDict->has(szName);
    AcDbObjectId objID;
    if (!bExist) {
        // 不存在,添加图像定义到字典,得到id
        pDict->setAt(szName, pImageDef, objID);
    }
    else
    {
        // 存在,获取图像定义的id
        pDict->getAt(szName, (AcDbObject*&)pImageDef,AcDb::kForWrite); // 获取图像定义.
        objID = pImageDef->objectId();
    }

    // 关闭字典和图像定义对象
    pDict->close();
    pImageDef->close();

    // 创建一个新的图像对象.
    AcDbRasterImage* pImage = new AcDbRasterImage;
    // 设置图像定义ID.
    es = pImage->setImageDefId(objID);
    if (es != Acad::eOk) {
        delete pImage;
        return;
    }

    // 获取模型空间的ID.
    AcDbObjectId modelId;
    modelId = acdbSymUtil()->blockModelSpaceId(pDb);

    // 写模式打开模型空间
    AcDbBlockTableRecord *pBTRecord;
    acdbOpenAcDbObject((AcDbObject*&)pBTRecord, modelId, AcDb::kForWrite);

    // 将图像添加到模型空间
    es = pBTRecord->appendAcDbEntity(pImage);
    // 关闭模型空间对象
    pBTRecord->close();

    // 获取图像对象的ID.
    AcDbObjectId entID = pImage->objectId();
    AcGePoint3d TempPoint3d(3.0, 0, 0);
    // 定义图像的右下角向量.
    AcGeVector3d LowerRightVector = TempPoint3d.asVector();
    AcGePoint3d TempPoint3d2(0, 1.5, 0);
   // 定义图像的平面向量.
    AcGeVector3d OnPlaneVector = TempPoint3d2.asVector();

    // 设置图像的方向
    if (pImage->setOrientation(org, LowerRightVector, OnPlaneVector) !=Adesk::kTrue) {
        ads_printf(_T("Set Orientation failed."));
        pImage->close();
        return;
    }

    // 设置图像的显示选项.
    pImage->setDisplayOpt(AcDbRasterImage::kShow, Adesk::kTrue);
    pImage->setDisplayOpt(AcDbRasterImage::kTransparent, Adesk::kTrue);

    // 创建一个图像定义反应器.
    AcDbObjectPointer<AcDbRasterImageDefReactor> rasterImageDefReactor;
    // 创建反应器对象.
    rasterImageDefReactor.create();

    // 设置图像对象作为反应器的所有者.
    es = rasterImageDefReactor->setOwnerId(pImage->objectId());

    // 如果设置成功.
    if (es == Acad::eOk) {
        AcDbObjectId defReactorId;
        // 为反应器对象分配一个ID.
        es = pDb->addAcDbObject(defReactorId, rasterImageDefReactor.object());

        // 如果分配ID成功.
        if (es == Acad::eOk) {
            // 设置图像对象的反应器ID.
            pImage->setReactorId(defReactorId);
           // 打开图像定义
            AcDbObjectPointer<AcDbRasterImageDef> rasterImagedef(pImage->imageDefId(), AcDb::kForWrite);
            // 如果成功
            if (rasterImagedef.openStatus() == Acad::eOk) {
               // 将反应器添加到图像定义中
                rasterImagedef->addPersistentReactor(defReactorId);
            }
        }
    }

    // 关闭图像对象.
    pImage->close();
}
```


# 系统剪贴板
下面是微软的剪贴板原理,大家看看就好,其实不用深入.因为IFox都写完了.

微软,OLE剪贴板:win32API
https://learn.microsoft.com/zh-c ... le2-olesetclipboard

微软,OLE剪贴板:使用 OLE 剪贴板机制,它是直接在堆上面创建数据源
https://learn.microsoft.com/zh-c ... anism?view=msvc-170

OLE剪贴板
https://www.cnblogs.com/henryzc/archive/2005/10/27/263003.html

## 一:概述
在Windows操作系统中存在两种剪贴板机制:
Windows标准剪贴板和OLE剪贴板机制.

标准的Windows剪贴板是一个被所有Windows应用程序共享的系统服务,因此它并没有自己的句柄或类.但你可以通过CWnd类的成员函数来管理剪贴板.

自从OLE(Object Linking and Embedding,对象链接和嵌入)诞生之后,Windows操作系统中便出现了第二种剪贴板机制——OLE剪贴板机制.
标准的Windows剪贴板API(Application Programming Interface,应用程序编程接口)依然可用,但是他已经被OLE数据传输机制来实现了.
OLE支持UDT(Uniform Data Transfer,统一数据传输),并可以通过拖放操作实现剪贴板的剪切/复制/粘贴等操作.
OLE剪贴板除了拥有标准Windows剪贴板的性能外,还支持传输用户自定义的剪贴板格式,并能够在传输数据时绑定OLE格式(如字体、字号等).OLE剪贴板机制将成为更为主要的数据传输机制.

本文将简要叙述标准Windows剪贴板的实现,并将重点放在讨论如何通过Visual C++实现OLE剪贴板上.

## 二:选择适当的剪贴板机制
在选择使用何种剪贴板机制时通常应遵循下面的原则:
1,如果应用程序在将来又可能具有新的性能(比如现在只需要传输纯文本,但将来有可能需要另外传输字体等特性),那么使用OLE剪贴板.
2,如果你正在使用一个OLE应用程序,或者你希望使用任何OLE特性(如拖放等)那么你应当使用OLE剪贴板机制.
3,如果你提供了OLE格式(字体/字号等),那么使用OLE剪贴板机制.

## 三:使用Windows标准剪贴板
大多数Windows下的应用程序支持剪切或复制数据到Windows剪贴板中,以及从剪贴板粘贴数据至目的地.在这个过程中,剪贴板数据格式在多种应用程序之间发生了变化.
表1,常用的标准剪贴板格式:
https://learn.microsoft.com/zh-c ... d-clipboard-formats

实现剪切和复制命令的函数,就要在你的应用程序中实现选定操作.
实现粘贴命令的函数,就需要请求剪贴板来检测它是否包含你的应用程序能够支持的数据.
下面的代码实现了复制命令,其它实现可仿照进行.

程序示例:
```c++
void CMyView::OnEditCopy() {
    if (!OpenClipboard()) {
        AfxMessageBox("无法打开剪贴板");
        return;
    }

    // 删除目前剪贴板的内容
    if (!EmptyClipboard()) {
        AfxMessageBox("无法清除剪贴板");
        return;
    }

    // 获取选定的数据

    // 检查是否为剪贴板支持的格式
    if (::SetClipboardData(CF_??, hData) == NULL) {
        // CF_?? 指定了剪贴板中数据的格式
        // 表1列出了标准的剪贴板格式
        AfxMessageBox("无法将数据复制到剪贴板当中");
        CloseClipboard();
        return;
    }

    // ...
    CloseClipboard();
}
```

## 四:使用OLE剪贴板机制
举个例子,关于OLE剪贴板的感性认识,同时说明你需要为OLE剪贴板做哪些事情:
Microsoft Excel为工作表注册了一个自定义的格式,这个格式能够比其它标准格式(如位图或纯文本等)提供更多的信息.
当此数据被粘贴到一个支持工作表的程序(比如Lotus 1-2-3)时,所有的原工作表中的公式和数值将被保留,并且还可能会根据需要被更新.
Excel同样将数据以OLE格式存放在剪贴板中,这样它就可以作为一个OLE对象被嵌入.
任何OLE文档包容器(Container)(比如Microsoft Word)能够将该数据作为嵌入对象粘贴进文档(比如通过“选择性粘贴”,可以在Word中粘贴进Excel工作表对象).
这个嵌入对象能够通过激活Microsoft Excel来进行修改(在Word中可以通过双击对象实现).
该工作表甚至可以被粘贴到一个绘图程序(比如的画笔).
当然,这时你无论如何都没有办法将其中的数据像在工作表中一样修改,因为它已经是图片了.

从上例总结一下,我们应当作的事情大致有:
注册自定义的格式,传输格式到剪贴板上,实现复制/剪切/粘贴.

注册自定义格式:
OLE剪贴板中的数据存在于多种格式.
当一个用户选择从剪贴板粘贴数据时,应用程序应当能够选择使用何种格式粘贴数据.
应用程序应当提供大部分格式的信息,除非用户指定使用某一种特定格式粘贴,比如只粘贴文字或只粘贴图片等.

Windows定义了很多能够通过剪贴板传输的标准格式(见表1),OLE也定义了很多特殊的格式.
应用程序可以通过获取更加详细的信息来注册他们自己的剪贴板格式.
这可以通过使用Win32API函数来实现:
RegisterClipboardFormat(lpszFormat);
参数lpzxFormat是指向一个字符串的指针,用以命名自定义的格式.该函数返回无符号整数,该数即为格式的ID号.

在注册了自定义的格式之后,便可以使用RegisterClipboardFormat函数的返回值来标识并使用该格式.

## 将格式传输到剪贴板上
要增加更多的格式到剪贴板上,你必须从COleClientItem或COleServerItem继承一个类,并且在该类中重载OnGetClipboardData函数.

1.建立一个COleDataSource对象.
2.传递该数据源到一个函数,用该函数通过访问COleDataSource::CacheGlobalData函数来将你的数据格式添加到支持的格式列表.
3.通过访问COleDataSource::CacheGlobalData,为每一个你向支持的格式添加标准格式.

程序示例:
```c++
COleDataSource* CMyItem::OnGetClipboardData(
    BOOL bIncludeLink,  // 是否包括链接数据
    LPPOINT pptOffset,  // 指向点对象的指针,用于存储数据的偏移量
    LPSIZE pSize        // 指向尺寸对象的指针,用于存储数据的尺寸
) {
    // 断言,确保对象是有效的
    ASSERT_VALID(this);

    // 如果没有服务器节点,则返回NULL
    if (m_pServerNode == NULL)
        return NULL;

    // 创建一个新的数据源对象
    COleDataSource* pDataSource = new COleDataSource;

    // 尝试/异常块开始
    TRY {
        // 获取与此项目相关的原始剪贴板数据
        GetNativeClipboardData(pDataSource);

        // 获取剪贴板数据,根据是否包含链接,以及偏移和大小信息
        GetClipboardData(pDataSource, bIncludeLink, pptOffset, pSize);
    }
    CATCH_ALL(e) {
        // 如果发生异常,删除数据源对象
        delete pDataSource;
        // 重新抛出异常
        THROW_LAST();
    }
    // 尝试/异常块结束
    END_CATCH_ALL
    // 断言,确保数据源对象是有效的
    ASSERT_VALID(pDataSource);
    // 返回数据源对象
    return pDataSource;
}
```

### 复制/剪切/粘贴数据
1.确定将要被复制的数据是一个本地数据还是一个嵌入对象或链接.
如果数据是一个嵌入对象或链接,创建一个指向被选定数据的COleClientItem指针.
如果数据是本地化的并且应用程序是一个服务器,那么从COleServerItem继承一个新的类,并创建该对象.否则,为数据建立一个COleDataSource对象.

2.访问选定对象的CopyToClipboard成员函数.

3.如果用户选择剪切命令而不是复制,那么从你的应用程序中删除那些数据.

程序示例:
```c++
void CMainView::OnEditCut() {
    // 确保有选择的内容
    ASSERT(m_pSelection != NULL);
   
    TRY {
        // 将选择的内容复制到剪贴板
        m_pSelection->CopyToClipboard(TRUE);
        // 清除选择的内容
        OnEditClear();
    }
    CATCH_ALL(e) {
        // 如果出现异常,显示剪贴板剪切失败的消息
        AfxMessageBox(IDP_CLIPBOARD_CUT_FAILED);
    }
    END_CATCH_ALL
}

void CMainView::OnEditCopy() {
    // 确保有选择的内容
    ASSERT(m_pSelection != NULL);
   
    TRY {
        // 将选择的内容复制到剪贴板
        m_pSelection->CopyToClipboard(TRUE);
    }
    CATCH_ALL(e) {
        // 如果出现异常,显示剪贴板复制失败的消息
        AfxMessageBox(IDP_CLIPBOARD_COPY_FAILED);
    }
    END_CATCH_ALL
}
```


### 从剪贴板粘贴数据
粘贴数据比复制更加复杂,因为你需要选择粘贴的格式.
1.在你的视中,实现OnEditPaste来处理用户从编辑菜单选择粘贴命令的操作.

2. 在OnEditPaste函数中建立一个COleDataObject对象并且访问它的AttachClipboard成员函数来将这个对象绑定到剪贴板.

3.访问COleDataObject::IsDataAvailable函数,来检查是否可以使用特殊的格式.
当然,你也可以通过循环使用COleDataObject::BeginEnumFormats来寻找其它格式直到你找到了最适合的格式.

4.粘贴数据.
程序示例:
```c++
CRectItem* CMainView::DoPasteItem(
    BOOL bLink,             // 是否链接到原始数据
    COleDataObject* pDataObject, // 指向剪贴板数据对象的指针
    CPoint* pPoint,         // 粘贴位置,如果为NULL,则使用默认位置
    CLIPFORMAT cfFormat     // 剪贴板格式
) {
    // 显示等待光标
    BeginWaitCursor();

    // 创建一个新的矩形项目
    CRectItem* pItem = GetDocument()->CreateItem();
    ASSERT_VALID(pItem); // 确保项目有效

    // 判断是否允许调整位置
    BOOL bAllowAdjust = (pPoint == NULL) ? TRUE : FALSE;

    // 定义一个用于剪贴板数据的对象
    COleDataObject clipboardData;

    // 如果没有提供数据对象,则从剪贴板获取数据
    if (pDataObject == NULL) {
        clipboardData.AttachClipboard();
        pDataObject = &clipboardData;
    }

    TRY {
        // 根据剪贴板格式处理粘贴
        if (cfFormat == CMainDoc::m_cfPrivate) {
            DoPasteNative(pDataObject, pPoint, pItem);
        } else if (!bLink && cfFormat == 0 && pDataObject->IsDataAvailable(CMainDoc::m_cfPrivate)) {
            DoPasteNative(pDataObject, pPoint, pItem);
        } else if (bAllowAdjust) {
            CPoint ptDef(10, -10); // 默认粘贴位置
            DoPasteStandard(bLink, pDataObject, &ptDef, pItem, cfFormat);
        } else {
            DoPasteStandard(bLink, pDataObject, pPoint, pItem, cfFormat);
        }

        // 如果允许调整位置,则调整项目位置
        if (bAllowAdjust) {
            GetDocument()->AdjustItemPosition(pItem);
        }
    }
    CATCH_ALL(e) {
        // 如果粘贴失败,输出错误信息并删除项目
        TRACE0("failed to embed/link an OLE object\n");
        pItem->Delete();
        pItem = NULL;
    }
    END_CATCH_ALL

    // 设置选择的项目并更新视图
    SetSelection(pItem, TRUE);
    GetDocument()->SetModifiedFlag();
    GetDocument()->UpdateAllViews(NULL, 0, pItem);

    // 隐藏等待光标
    EndWaitCursor();

    // 返回创建的项目
    return pItem;
}

void CMainView::OnEditPaste() {
    // 创建一个剪贴板数据对象并附加到剪贴板
    COleDataObject clipboardData;
    clipboardData.AttachClipboard();

    // 调用粘贴项目函数
    DoPasteItem(FALSE, &clipboardData);

    // 更新所有视图
    UpdateAllViews();
}
```

说明,将粘贴操作(如OnEditPaste函数)与实现粘贴的函数(如DoPasteItem)分开的最大优点在于,
当数据被拖放到你的应用程序中时,可以使用同样的粘贴代码.
比如你可以在OnDrop函数中访问DoPasteItem函数来重用代码.
另外,程序代码中的DoPasteNative和DoPasteStandard函数仅仅说明一个概念,因此不再实现.
(完)
发表于 2024-9-4 15:04:38 | 显示全部楼层
大佬出没,路过点赞
您需要登录后才可以回帖 登录 | 注册

本版积分规则

小黑屋|手机版|CAD论坛|CAD教程|CAD下载|联系我们|关于明经|明经通道 ( 粤ICP备05003914号 )  
©2000-2023 明经通道 版权所有 本站代码,在未取得本站及作者授权的情况下,不得用于商业用途

GMT+8, 2024-11-25 07:02 , Processed in 0.229366 second(s), 23 queries , Gzip On.

Powered by Discuz! X3.4

Copyright © 2001-2021, Tencent Cloud.

快速回复 返回顶部 返回列表